mirror of
https://github.com/langflow-ai/langflow.git
synced 2026-07-23 17:31:33 +08:00
fix: Raise on an undecryptable secret at the variable-resolution (#13681)
fix: Raise on an undecryptable secret at the variable-resolution boundary Co-authored-by: Janardan S Kavia <janardanskavia@Janardans-MacBook-Pro.local>
This commit is contained in:
committed by
GitHub
parent
b033bfa22b
commit
2b8f7f90a0
2730
.secrets.baseline
2730
.secrets.baseline
File diff suppressed because it is too large
Load Diff
@ -219,7 +219,15 @@ class DatabaseVariableService(VariableService, Service):
|
||||
if variable.type == CREDENTIAL_TYPE:
|
||||
from pydantic import SecretStr
|
||||
|
||||
return SecretStr(auth_utils.decrypt_api_key(variable.value))
|
||||
decrypted = auth_utils.decrypt_api_key(variable.value)
|
||||
if not decrypted:
|
||||
msg = (
|
||||
f"Could not decrypt credential variable '{name}'. The stored value cannot be "
|
||||
"decrypted with the current LANGFLOW_SECRET_KEY — it may have been encrypted "
|
||||
"with a different key."
|
||||
)
|
||||
raise ValueError(msg)
|
||||
return SecretStr(decrypted)
|
||||
# GENERIC type - return as-is
|
||||
return variable.value
|
||||
|
||||
|
||||
@ -1,8 +1,11 @@
|
||||
import secrets
|
||||
from datetime import datetime
|
||||
from unittest.mock import patch
|
||||
from uuid import uuid4
|
||||
|
||||
import pytest
|
||||
from cryptography.fernet import Fernet
|
||||
from langflow.services.auth.utils import ensure_fernet_key
|
||||
from langflow.services.database.models.variable.model import VariableUpdate
|
||||
from langflow.services.deps import get_settings_service
|
||||
from langflow.services.variable.constants import CREDENTIAL_TYPE, GENERIC_TYPE
|
||||
@ -103,6 +106,42 @@ async def test_get_variable__typeerror(service, session: AsyncSession):
|
||||
assert "purpose is to prevent the exposure of value" in str(exc.value)
|
||||
|
||||
|
||||
async def test_get_variable__credential_decrypt_failure(service, session: AsyncSession):
|
||||
"""Store credential under SECRET_KEY=A, resolve with SECRET_KEY=B → raises naming the variable.
|
||||
|
||||
Uses ensure_fernet_key (the real SECRET_KEY→Fernet derivation path) so the test exercises
|
||||
the exact mismatch scenario described in the ticket: a key rotation or missing persisted key.
|
||||
The test auth service (lfx stub) is a passthrough, so we patch at the auth_utils boundary.
|
||||
"""
|
||||
secret_key_a = secrets.token_urlsafe(32)
|
||||
secret_key_b = secrets.token_urlsafe(32)
|
||||
fernet_a = Fernet(ensure_fernet_key(secret_key_a))
|
||||
fernet_b = Fernet(ensure_fernet_key(secret_key_b))
|
||||
|
||||
user_id = uuid4()
|
||||
name = "MY_CRED"
|
||||
|
||||
# Phase 1: store credential encrypted under SECRET_KEY = secret_key_a.
|
||||
with patch(
|
||||
"langflow.services.variable.service.auth_utils.encrypt_api_key",
|
||||
side_effect=lambda v: fernet_a.encrypt(v.encode()).decode(),
|
||||
):
|
||||
await service.create_variable(user_id, name, "secret123", type_=CREDENTIAL_TYPE, session=session)
|
||||
|
||||
# Phase 2: resolve with SECRET_KEY = secret_key_b (mismatched) — Fernet raises InvalidToken → "".
|
||||
def decrypt_with_wrong_key(ciphertext: str) -> str:
|
||||
try:
|
||||
return fernet_b.decrypt(ciphertext.encode()).decode()
|
||||
except Exception:
|
||||
return ""
|
||||
|
||||
with (
|
||||
patch("langflow.services.variable.service.auth_utils.decrypt_api_key", side_effect=decrypt_with_wrong_key),
|
||||
pytest.raises(ValueError, match=r"MY_CRED.*SECRET_KEY"),
|
||||
):
|
||||
await service.get_variable(user_id, name, "", session=session)
|
||||
|
||||
|
||||
async def test_list_variables(service, session: AsyncSession):
|
||||
user_id = uuid4()
|
||||
names = ["name1", "name2", "name3"]
|
||||
|
||||
Reference in New Issue
Block a user