fix: warn on empty value and decrypt failure in get_all variable listing (#13741)

Co-authored-by: Janardan S Kavia <janardanskavia@Janardans-MacBook-Pro.local>
This commit is contained in:
Janardan Singh Kavia
2026-06-19 13:30:49 -04:00
committed by GitHub
parent 2ff192129a
commit 2b7b113049
2 changed files with 70 additions and 2 deletions

View File

@ -238,9 +238,16 @@ class DatabaseVariableService(VariableService, Service):
for variable in variables:
value = None
if variable.type == GENERIC_TYPE:
if not variable.value:
await logger.awarning("Variable '%s' has no stored value — skipping.", variable.name)
continue
value = auth_utils.decrypt_api_key(variable.value)
if not value:
# If decryption fails (likely due to encryption by different key), skip this variable
await logger.awarning(
"Variable '%s' could not be decrypted — likely encrypted with a different "
"LANGFLOW_SECRET_KEY. Skipping.",
variable.name,
)
continue
# Model validate will set value to None if credential type

View File

@ -1,6 +1,6 @@
import secrets
from datetime import datetime
from unittest.mock import patch
from unittest.mock import AsyncMock, MagicMock, patch
from uuid import uuid4
import pytest
@ -394,6 +394,67 @@ async def test_update_generic_variable_with_fernet_signature_fails(service, sess
await service.update_variable(user_id, "TEST_VAR", "gAAAAABthis-looks-like-encrypted", session=session)
async def test_get_all__empty_value_warns_and_skips(service, session: AsyncSession):
"""get_all warns and skips GENERIC variables whose stored value is None or empty."""
user_id = uuid4()
# Create a normal generic variable, then manually blank its value to simulate a bad DB row.
var = await service.create_variable(user_id, "EMPTY_VAR", "initial", type_=GENERIC_TYPE, session=session)
var.value = ""
session.add(var)
await session.flush()
mock_logger = MagicMock()
mock_logger.awarning = AsyncMock()
with patch("langflow.services.variable.service.logger", mock_logger):
result = await service.get_all(user_id, session=session)
# Variable should be excluded from results.
assert not any(v.name == "EMPTY_VAR" for v in result)
# Warning must name the variable and mention empty value.
warning_calls = [str(c) for c in mock_logger.awarning.call_args_list]
assert any("EMPTY_VAR" in c for c in warning_calls)
assert any("no stored value" in c for c in warning_calls)
async def test_get_all__decrypt_failure_warns_and_skips(service, session: AsyncSession):
"""get_all warns with a key-mismatch message when decrypt returns empty for a non-empty value."""
user_id = uuid4()
await service.create_variable(user_id, "MY_VAR", "real_value", type_=GENERIC_TYPE, session=session)
mock_logger = MagicMock()
mock_logger.awarning = AsyncMock()
# Simulate decrypt returning "" (key mismatch) without touching the stored value.
with (
patch("langflow.services.variable.service.auth_utils.decrypt_api_key", return_value=""),
patch("langflow.services.variable.service.logger", mock_logger),
):
result = await service.get_all(user_id, session=session)
# Variable should be excluded from results.
assert not any(v.name == "MY_VAR" for v in result)
# Warning must name the variable and mention SECRET_KEY.
warning_calls = [str(c) for c in mock_logger.awarning.call_args_list]
assert any("MY_VAR" in c for c in warning_calls)
assert any("SECRET_KEY" in c for c in warning_calls)
async def test_get_all__healthy_generic_variable_included(service, session: AsyncSession):
"""get_all includes GENERIC variables that decrypt successfully — no warnings emitted."""
user_id = uuid4()
await service.create_variable(user_id, "GOOD_VAR", "good_value", type_=GENERIC_TYPE, session=session)
mock_logger = MagicMock()
mock_logger.awarning = AsyncMock()
with patch("langflow.services.variable.service.logger", mock_logger):
result = await service.get_all(user_id, session=session)
assert any(v.name == "GOOD_VAR" for v in result)
mock_logger.awarning.assert_not_called()
async def test_create_credential_variable_with_fernet_signature_succeeds(service, session: AsyncSession):
"""Test that CREDENTIAL variables can have values that look like Fernet tokens (they get encrypted anyway)."""
user_id = uuid4()