# Langflow: Weak Fernet Key via random.seed()

Langflow derived its Fernet credential key from Python's non-crypto random seeded with SECRET_KEY, so anyone reading secret_key and the DB decrypts all stored credentials.

- Payout: Research
- Severity: critical
- Program: Langflow
- Found by: akshatgit, north-echo, AntonioABLima
- Disclosed: 2026-10-05
- CVE: CVE-2026-9205
- Write-up quality: 7/10
- Original: https://github.com/advisories/GHSA-jxw3-mjmx-3pqm
- Library page: https://bugbountylibrary.com/r/ghsa%3AGHSA-jxw3-mjmx-3pqm
- Tags: area:ai domain:ai domain:web impact:ato impact:data-leak kind:research platform:ghsa sev:critical tech:python technique:chained technique:source-review vuln:crypto vuln:secrets-leak

**Target:** Langflow auth service credential encryption (secret_key file + SQLite variable table)
**Root cause:** For SECRET_KEY shorter than 32 chars, the Fernet key is generated by seeding Python's Mersenne Twister PRNG with the secret, making the key fully deterministic; for longer keys the raw secret is used directly as the Fernet key.
**Steps:**
1. Read `/app/data/.cache/langflow/secret_key` (e.g. via the MCP path traversal GHSA-95rw-c7w3-xh7f) and the SQLite DB `variable` table rows where `type='Credential'`.
2. If secret_key < 32 chars: `random.seed(secret_key)`; `key = bytes(random.getrandbits(8) for _ in range(32))`; `base64.urlsafe_b64encode(key)`.
3. If secret_key >= 32 chars: pad with `=` to a multiple of 4 and use it directly as the Fernet key.
4. Decrypt each ciphertext with `Fernet(fernet_key).decrypt(ciphertext)` offline.
**How found:** Code review of `_ensure_valid_key` in the auth service spotted the non-cryptographic `random.seed(raw_key)` branch for short secrets and the raw-key branch for long ones, then verified by storing a sentinel credential via the API and recovering it from raw DB ciphertext on v1.7.3.
**Exploitation:** Chain with the MCP path traversal: any authenticated low-privilege user reads the secret_key file and the langflow.db SQLite file, then decrypts every Credential-type variable offline with no brute force, yielding all users' LLM API keys, DB passwords and OAuth tokens with no server-side log traces.
**Escalation:** Chained with the MCP path traversal (GHSA-95rw-c7w3-xh7f) to turn a file-read into instance-wide credential exfiltration across all users in multi-tenant deployments.
**Insight:** When a secret is used as a PRNG seed to stretch it into a key, the output is a pure function of the seed — grep for `random.seed`, `Random(`, and `getrandbits` in key-derivation paths; also check whether the 'safe' branch just reuses the raw secret as key material, making file-read equivalent to full decryption.

