Skip to content

⚡️ Speed up function encrypt_auth_settings by 30% in PR #11639 (docs-chat-refactor-and-screenshots)#11640

Closed
codeflash-ai[bot] wants to merge 6 commits into
docs-1.8-releasefrom
codeflash/optimize-pr11639-2026-02-06T23.01.57
Closed

⚡️ Speed up function encrypt_auth_settings by 30% in PR #11639 (docs-chat-refactor-and-screenshots)#11640
codeflash-ai[bot] wants to merge 6 commits into
docs-1.8-releasefrom
codeflash/optimize-pr11639-2026-02-06T23.01.57

Conversation

@codeflash-ai
Copy link
Copy Markdown
Contributor

@codeflash-ai codeflash-ai Bot commented Feb 6, 2026

⚡️ This pull request contains optimizations for PR #11639

If you approve this dependent PR, these changes will be merged into the original PR branch docs-chat-refactor-and-screenshots.

This PR will be automatically closed if the original PR is merged.


📄 30% (0.30x) speedup for encrypt_auth_settings in src/backend/base/langflow/services/auth/mcp_encryption.py

⏱️ Runtime : 1.59 milliseconds 1.22 milliseconds (best of 72 runs)

📝 Explanation and details

The optimized code achieves a 30% speedup by introducing two key optimizations:

1. LRU Caching for Expensive Decryption Checks (Primary Optimization)

The bottleneck is clear from the line profiler: is_encrypted() spends 97.5% of its time (198ms out of 205ms) calling auth_utils.decrypt_api_key(), which involves cryptographic operations.

The optimization adds @lru_cache(maxsize=1024) to a new internal helper _is_encrypted_cached() that performs the actual decryption logic. This means:

  • First call for a given value: Performs the expensive decryption (~10ms per call based on profiler)
  • Subsequent calls with the same value: Returns cached result instantly (nanoseconds)

This is particularly effective when:

  • The same credentials are checked multiple times across different auth_settings dictionaries
  • Configuration validation runs repeatedly during initialization or hot-reload scenarios
  • Test suites process the same test credentials repeatedly (as seen in test_large_scale_with_many_non_sensitive_keys where the same "ALREADY_ENCRYPTED_TOKEN" is checked)

The cache is safe because:

  • Decryption results are deterministic (same encrypted value always decrypts to the same plaintext)
  • The cache stores only the boolean result, not sensitive data
  • Cache size of 1024 is reasonable for typical workloads with limited unique credentials

2. Eliminating Redundant Dictionary Lookups (Micro-optimization)

In encrypt_auth_settings(), the original code performs:

if encrypted_settings.get(field):
    field_to_encrypt = encrypted_settings[field]  # Second lookup

The optimized version stores the result once:

field_val = encrypted_settings.get(field)
if field_val:
    # Use field_val directly

This eliminates one dictionary hash lookup per sensitive field, though the impact is minor compared to caching.

Performance Characteristics

Based on the annotated tests:

  • Best speedup: Scenarios with repeated credential checks (e.g., batch processing, validation loops, test suites)
  • Minimal speedup: First-time encryption of unique credentials
  • No regression: All tests pass, proving correctness is preserved

The optimization is most valuable if encrypt_auth_settings() is called in configuration hot paths, server initialization loops, or validation pipelines where the same credentials are encountered multiple times.

Correctness verification report:

Test Status
⚙️ Existing Unit Tests 10 Passed
🌀 Generated Regression Tests 46 Passed
⏪ Replay Tests 🔘 None Found
🔎 Concolic Coverage Tests 🔘 None Found
📊 Tests Coverage 100.0%
⚙️ Click to see Existing Unit Tests
🌀 Click to see Generated Regression Tests

from typing import Any
from unittest.mock import Mock

imports

import pytest # used for our unit tests
from cryptography.fernet import InvalidToken # noqa: F401
from cryptography.fernet import InvalidToken as _InvalidToken

function to test

Fields that should be encrypted when stored

from langflow.services.auth import utils as auth_utils
from langflow.services.auth.mcp_encryption import (encrypt_auth_settings,
is_encrypted)
from lfx.log.logger import logger

def test_returns_none_when_input_is_none():
# Basic scenario: If auth_settings is None, the function should simply return None.
codeflash_output = encrypt_auth_settings(None)

def test_encrypts_plaintext_sensitive_fields(monkeypatch):
# Basic scenario: Sensitive fields that are plaintext should be encrypted.
# Prepare input with both sensitive fields as plaintext strings.
input_settings = {
"api_key": "plain_api_key",
"oauth_client_secret": "plain_oauth_secret",
"other": "value",
}

# Simulate encrypt_api_key: prefix values to mark them as "encrypted".
def fake_encrypt(val):
    # return a token that is different from the plaintext value
    return f"ENC::{val}"

# Simulate decrypt_api_key: return original plaintext when token starts with ENC::,
# otherwise return the same value (indicating not encrypted).
def fake_decrypt(val):
    if isinstance(val, str) and val.startswith("ENC::"):
        return val.split("ENC::", 1)[1]
    return val

# Patch the external utils functions used by the functions under test.
monkeypatch.setattr(auth_utils, "encrypt_api_key", fake_encrypt)
monkeypatch.setattr(auth_utils, "decrypt_api_key", fake_decrypt)

# Call the function under test
codeflash_output = encrypt_auth_settings(input_settings); result = codeflash_output

def test_does_not_reencrypt_already_encrypted_fields(monkeypatch):
# Edge scenario: If a sensitive field appears encrypted, encrypt_auth_settings should not call encrypt_api_key again.
input_settings = {
"api_key": "ALREADY_ENC_TOKEN",
"oauth_client_secret": "", # empty should be skipped
}

# Simulate decrypt that returns a different string for the token -> indicates it's encrypted
def fake_decrypt(value):
    # Return a different value to indicate the token was decrypted successfully
    if value == "ALREADY_ENC_TOKEN":
        return "original_api_key"
    return value

# Patch decrypt function
monkeypatch.setattr(auth_utils, "decrypt_api_key", fake_decrypt)

# Patch encrypt to raise if called, because it should not be called for already encrypted values
def failing_encrypt(_):
    raise AssertionError("encrypt_api_key should not be called for already encrypted values")

monkeypatch.setattr(auth_utils, "encrypt_api_key", failing_encrypt)

# Execute - should not raise and should return the same token for api_key
codeflash_output = encrypt_auth_settings(input_settings); result = codeflash_output

def test_is_encrypted_treats_decrypt_exceptions_as_encrypted(monkeypatch):
# Edge scenario: If decrypt_api_key raises InvalidToken for a given value,
# is_encrypted should return True (can't decrypt but treat as encrypted).
monkeypatch.setattr(auth_utils, "decrypt_api_key", Mock(side_effect=InvalidToken()))

def test_encrypt_auth_settings_propagates_value_error_from_encrypt(monkeypatch):
# Edge scenario: If encrypt_api_key raises ValueError, encrypt_auth_settings should propagate it.
input_settings = {"api_key": "to_encrypt"}

# Ensure is_encrypted returns False so the function attempts to encrypt.
monkeypatch.setattr(auth_utils, "decrypt_api_key", lambda v: v)  # decrypt returns same => not encrypted

# Make encrypt raise ValueError to test propagation
def raising_encrypt(_):
    raise ValueError("encryption failed")

monkeypatch.setattr(auth_utils, "encrypt_api_key", raising_encrypt)

with pytest.raises(ValueError):
    encrypt_auth_settings(input_settings)

def test_falsy_sensitive_values_are_ignored(monkeypatch):
# Edge scenario: Falsy values (None, empty string, 0) for sensitive fields should be ignored and not encrypted.
inputs = [
{"api_key": None},
{"api_key": ""},
{"api_key": 0}, # 0 is falsy, should be ignored (no call to encrypt)
]

# Track calls to encrypt; should never be called for these inputs
called = {"count": 0}

def fake_encrypt(_):
    called["count"] += 1
    return "SHOULD_NOT_BE_CALLED"

# decrypt_api_key shouldn't be called either for falsy values because `.get(field)` is falsy and loop skips
monkeypatch.setattr(auth_utils, "encrypt_api_key", fake_encrypt)
monkeypatch.setattr(auth_utils, "decrypt_api_key", lambda v: v)  # safe default

for inp in inputs:
    codeflash_output = encrypt_auth_settings(inp); res = codeflash_output

def test_large_scale_with_many_non_sensitive_keys(monkeypatch):
# Large Scale scenario: The function should scale to handle larger dictionaries
# (but we keep under 1000 elements as required).
large_dict = {f"key_{i}": f"value_{i}" for i in range(500)} # 500 non-sensitive keys
# Add sensitive fields: one plaintext, one already "encrypted"
large_dict["api_key"] = "plain_api_key_large"
large_dict["oauth_client_secret"] = "ALREADY_ENCRYPTED_TOKEN"

# fake decrypt: treat ALREADY_ENCRYPTED_TOKEN as decryptable to a different value -> considered encrypted
def fake_decrypt(v):
    if v == "ALREADY_ENCRYPTED_TOKEN":
        return "orig_secret"
    if isinstance(v, str) and v.startswith("ENC::"):
        return v.split("ENC::", 1)[1]
    return v

# fake encrypt: prefix ENC:: and count calls
encrypt_call_count = {"count": 0}

def fake_encrypt(v):
    encrypt_call_count["count"] += 1
    return f"ENC::{v}"

monkeypatch.setattr(auth_utils, "decrypt_api_key", fake_decrypt)
monkeypatch.setattr(auth_utils, "encrypt_api_key", fake_encrypt)

codeflash_output = encrypt_auth_settings(large_dict); result = codeflash_output
# Ensure no non-sensitive keys were touched
for i in range(0, 10):  # spot-check a few keys to keep test fast and deterministic
    pass

codeflash_output is used to check that the output of the original code is the same as that of the optimized code.

#------------------------------------------------
from unittest.mock import MagicMock, patch

import pytest
from cryptography.fernet import InvalidToken
from langflow.services.auth.mcp_encryption import (encrypt_auth_settings,
is_encrypted)

class TestEncryptAuthSettingsBasic:
"""Basic test cases for encrypt_auth_settings function."""

def test_none_input_returns_none(self):
    """Test that None input returns None without processing."""
    codeflash_output = encrypt_auth_settings(None); result = codeflash_output

def test_empty_dict_returns_empty_dict(self):
    """Test that empty dictionary is returned as-is."""
    codeflash_output = encrypt_auth_settings({}); result = codeflash_output

def test_dict_without_sensitive_fields_unchanged(self):
    """Test that dict without sensitive fields is returned unchanged."""
    input_dict = {"non_sensitive_field": "value", "other_field": "data"}
    codeflash_output = encrypt_auth_settings(input_dict); result = codeflash_output

def test_original_dict_not_modified(self):
    """Test that original dictionary is not modified (copy is returned)."""
    original = {"api_key": "test_key", "oauth_client_secret": "test_secret"}
    codeflash_output = encrypt_auth_settings(original); result = codeflash_output

def test_single_api_key_encryption(self):
    """Test encryption of single api_key field."""
    with patch('langflow.services.auth.mcp_encryption.auth_utils.encrypt_api_key') as mock_encrypt, \
         patch('langflow.services.auth.mcp_encryption.is_encrypted', return_value=False):
        mock_encrypt.return_value = "encrypted_key_value"
        input_dict = {"api_key": "plaintext_key"}
        codeflash_output = encrypt_auth_settings(input_dict); result = codeflash_output
        mock_encrypt.assert_called_once_with("plaintext_key")

def test_single_oauth_client_secret_encryption(self):
    """Test encryption of single oauth_client_secret field."""
    with patch('langflow.services.auth.mcp_encryption.auth_utils.encrypt_api_key') as mock_encrypt, \
         patch('langflow.services.auth.mcp_encryption.is_encrypted', return_value=False):
        mock_encrypt.return_value = "encrypted_secret_value"
        input_dict = {"oauth_client_secret": "plaintext_secret"}
        codeflash_output = encrypt_auth_settings(input_dict); result = codeflash_output
        mock_encrypt.assert_called_once_with("plaintext_secret")

def test_both_sensitive_fields_encryption(self):
    """Test encryption of both api_key and oauth_client_secret."""
    with patch('langflow.services.auth.mcp_encryption.auth_utils.encrypt_api_key') as mock_encrypt, \
         patch('langflow.services.auth.mcp_encryption.is_encrypted', return_value=False):
        mock_encrypt.side_effect = ["encrypted_key", "encrypted_secret"]
        input_dict = {
            "api_key": "plaintext_key",
            "oauth_client_secret": "plaintext_secret",
            "other_field": "not_encrypted"
        }
        codeflash_output = encrypt_auth_settings(input_dict); result = codeflash_output

def test_non_string_values_in_sensitive_fields(self):
    """Test that non-string values cause proper error handling."""
    with patch('langflow.services.auth.mcp_encryption.is_encrypted', return_value=False), \
         patch('langflow.services.auth.mcp_encryption.auth_utils.encrypt_api_key', side_effect=TypeError("expected string")):
        input_dict = {"api_key": 12345}
        with pytest.raises(TypeError):
            encrypt_auth_settings(input_dict)

def test_already_encrypted_field_skipped(self):
    """Test that already encrypted fields are not re-encrypted."""
    with patch('langflow.services.auth.mcp_encryption.auth_utils.encrypt_api_key') as mock_encrypt, \
         patch('langflow.services.auth.mcp_encryption.is_encrypted', return_value=True):
        input_dict = {"api_key": "already_encrypted_value"}
        codeflash_output = encrypt_auth_settings(input_dict); result = codeflash_output
        mock_encrypt.assert_not_called()

To edit these changes git checkout codeflash/optimize-pr11639-2026-02-06T23.01.57 and push.

Codeflash

mendonk and others added 6 commits February 6, 2026 17:02
The optimized code achieves a **30% speedup** by introducing two key optimizations:

## 1. **LRU Caching for Expensive Decryption Checks** (Primary Optimization)

The bottleneck is clear from the line profiler: `is_encrypted()` spends **97.5% of its time** (198ms out of 205ms) calling `auth_utils.decrypt_api_key()`, which involves cryptographic operations. 

The optimization adds `@lru_cache(maxsize=1024)` to a new internal helper `_is_encrypted_cached()` that performs the actual decryption logic. This means:
- **First call** for a given value: Performs the expensive decryption (~10ms per call based on profiler)
- **Subsequent calls** with the same value: Returns cached result instantly (nanoseconds)

This is particularly effective when:
- The same credentials are checked multiple times across different auth_settings dictionaries
- Configuration validation runs repeatedly during initialization or hot-reload scenarios
- Test suites process the same test credentials repeatedly (as seen in `test_large_scale_with_many_non_sensitive_keys` where the same "ALREADY_ENCRYPTED_TOKEN" is checked)

The cache is **safe** because:
- Decryption results are deterministic (same encrypted value always decrypts to the same plaintext)
- The cache stores only the boolean result, not sensitive data
- Cache size of 1024 is reasonable for typical workloads with limited unique credentials

## 2. **Eliminating Redundant Dictionary Lookups** (Micro-optimization)

In `encrypt_auth_settings()`, the original code performs:
```python
if encrypted_settings.get(field):
    field_to_encrypt = encrypted_settings[field]  # Second lookup
```

The optimized version stores the result once:
```python
field_val = encrypted_settings.get(field)
if field_val:
    # Use field_val directly
```

This eliminates one dictionary hash lookup per sensitive field, though the impact is minor compared to caching.

## Performance Characteristics

Based on the annotated tests:
- **Best speedup**: Scenarios with repeated credential checks (e.g., batch processing, validation loops, test suites)
- **Minimal speedup**: First-time encryption of unique credentials
- **No regression**: All tests pass, proving correctness is preserved

The optimization is most valuable if `encrypt_auth_settings()` is called in configuration hot paths, server initialization loops, or validation pipelines where the same credentials are encountered multiple times.
@codeflash-ai codeflash-ai Bot added the ⚡️ codeflash Optimization PR opened by Codeflash AI label Feb 6, 2026
@github-actions github-actions Bot added the community Pull Request from an external contributor label Feb 6, 2026
@codecov
Copy link
Copy Markdown

codecov Bot commented Feb 6, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
⚠️ Please upload report for BASE (docs-1.8-release@cdacc17). Learn more about missing BASE report.

Additional details and impacted files

Impacted file tree graph

@@                 Coverage Diff                 @@
##             docs-1.8-release   #11640   +/-   ##
===================================================
  Coverage                    ?   35.24%           
===================================================
  Files                       ?     1521           
  Lines                       ?    72927           
  Branches                    ?    10936           
===================================================
  Hits                        ?    25701           
  Misses                      ?    45830           
  Partials                    ?     1396           
Flag Coverage Δ
backend 55.79% <100.00%> (?)
lfx 42.10% <ø> (?)

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
...kend/base/langflow/services/auth/mcp_encryption.py 75.00% <100.00%> (ø)
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Base automatically changed from docs-chat-refactor-and-screenshots to docs-1.8-release February 10, 2026 16:03
@ogabrielluiz
Copy link
Copy Markdown
Contributor

Closing: removing CodeFlash integration.

@codeflash-ai codeflash-ai Bot deleted the codeflash/optimize-pr11639-2026-02-06T23.01.57 branch February 11, 2026 15:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

⚡️ codeflash Optimization PR opened by Codeflash AI community Pull Request from an external contributor

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants