Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
90 commits
Select commit Hold shift + click to select a range
2c3fe7f
docs: update component links to individual pages (#10706)
mendonk Nov 25, 2025
168b84c
fix: avoid updating Message if ChatOutput is connected to ChatInput (…
keval718 Nov 25, 2025
0833dfb
Feat: Runflow optimization and improved dropdown behavior (#10720)
HzaRashid Nov 25, 2025
5040efc
fix: Add dynamic tool mode descriptions for agent integration (#10744)
Cristhianzl Nov 27, 2025
c46d31c
fix: Add profile picture management and API endpoints (#10763)
Cristhianzl Dec 1, 2025
2d735a7
deps: upgrade altk (#10804)
jordanrfrazier Dec 1, 2025
17aff85
fix: use running event loop to fix asyncio error when calling mcp too…
jordanrfrazier Dec 1, 2025
956baf6
fix: Improve file processing robustness and error feedback (#10781)
Cristhianzl Dec 1, 2025
4529e92
fix: resolve merge conflict (#10831)
keval718 Dec 1, 2025
e89bee0
fix: fixed warning on console for nested button (#10724) (#10832)
olayinkaadelakun Dec 1, 2025
f7a82f3
fix: fixed warning on console (#10745) (#10830)
olayinkaadelakun Dec 1, 2025
73120ad
fix: mask value to hide null field being returned (#10778) (#10829)
olayinkaadelakun Dec 1, 2025
e321e3e
Fix: Allow refresh list button to stay stagnant while zoom (Safari) (…
olayinkaadelakun Dec 1, 2025
423419e
feat: Add superuser support for running any user flow (#10808)
Cristhianzl Dec 1, 2025
a562670
Revert "feat: Add superuser support for running any user flow (#10808)"
Cristhianzl Dec 2, 2025
4df89f1
fix: Ollama models list in Agent component (#10814)
HimavarshaVS Dec 2, 2025
c66c679
Fix: Ensure Default Tab is Credential (#10779) (#10826)
olayinkaadelakun Dec 2, 2025
2a3d424
chore: update cuga version (#10737) (#10738)
jordanrfrazier Dec 2, 2025
731cc8b
chore: Remove DataFrameToToolsetComponent and related tests (#10845)
edwinjosechittilappilly Dec 3, 2025
3a2395b
fix: Handle GCP JSON parsing credentials (#10859)
erichare Dec 3, 2025
9be8720
fix: anthropic constants (#10862)
edwinjosechittilappilly Dec 3, 2025
5b09d60
fix: Add feature flag check to simplified_run_flow_session (#10863)
edwinjosechittilappilly Dec 3, 2025
97164d8
fix: Improve the debugging messages on startup (#10864)
erichare Dec 3, 2025
c22ebff
fix: Don't fail if doc column is missing (#10746) (#10872)
erichare Dec 3, 2025
7f5940e
add x-api-key auth option
Cristhianzl Dec 4, 2025
d04142b
fix(auth): Disallow refresh token access to API endpoints
mpawlow Dec 2, 2025
a1ce944
fix: Properly support the Batch Run component for watsonX models (#10…
erichare Dec 4, 2025
a9ef7fb
fix: Image upload for Gemini/Anthropic (#10880)
erichare Dec 4, 2025
05d5a1e
fix: Improve the default startup logging for readability (#10894)
erichare Dec 4, 2025
efcae53
Fix: lfx serve aysncio event loop error (#10888)
HzaRashid Dec 4, 2025
1174a6a
fix: Update LangflowCounts component to format star and Discord count…
viktoravelino Dec 4, 2025
99e73b6
Fix: update lfx serve tests to mock the .serve() to prevent hanging …
HzaRashid Dec 5, 2025
3beffea
Fix: lfx run agent _noopresult not iterable error (#10893)
HzaRashid Dec 5, 2025
f312f22
Fix: lfx run agent _noopresult not iterable error (#10911)
HzaRashid Dec 5, 2025
5ccd44e
fix: Add graceful subprocess cleanup during shutdown (#10906)
Cristhianzl Dec 5, 2025
a62b851
fix(workflows): include src/lfx/uv.lock in git add command to ensure …
Cristhianzl Dec 7, 2025
8245904
chore(nightly_build.yml): remove unnecessary directory change for lfx…
Cristhianzl Dec 7, 2025
44a9f70
chore(release_nightly): update build command to include --no-sources …
Cristhianzl Dec 7, 2025
5b7e332
chore(chat.py): remove unused future annotations import to clean up code
Cristhianzl Dec 7, 2025
3c1d0d2
fix(chat.py): add future annotations import for better type hinting s…
Cristhianzl Dec 7, 2025
6566275
chore: print version
Adam-Aghili Dec 8, 2025
22c9237
chore: use release_tag as version
Adam-Aghili Dec 8, 2025
5cffeae
fix: --prerelease=allow
Adam-Aghili Dec 8, 2025
6a0fd4d
fix: correctly raise file not found errors in File GET endpoints (#1…
jordanrfrazier Dec 8, 2025
2f157c9
fix: image pathing to operate with s3 storage (#10919) (#10929)
jordanrfrazier Dec 8, 2025
5550861
Feat: migrate MCP transport from SSE to streamable http (#10934)
HzaRashid Dec 8, 2025
5e54758
refactor(deps.py): reorganize imports for clarity and compliance with…
Cristhianzl Dec 8, 2025
af529b3
fix: update sidebar icon styles to maintain backward compatibility (#…
viktoravelino Dec 10, 2025
b6ed2bc
fix: Add empty input check in ALTKAgent for Anthropic (#10926)
jordanrfrazier Dec 10, 2025
f184989
fix: add condition to not make folder download fail when flow has Not…
lucaseduoli Dec 10, 2025
7b66dfb
fix: Enhance error handling for langchain-core version compatibility …
ogabrielluiz Dec 10, 2025
76af6b9
fix: Restrict message and session access to flow owners (#10973)
Cristhianzl Dec 11, 2025
c160933
Fix: lfx run with agent component throws '_NoopResult' object is not …
HzaRashid Dec 11, 2025
3200a2e
fix: Support tool mode for components that have no inputs (#10982)
erichare Dec 11, 2025
ac38023
fix: (Cherry Pick) default Ollama base url (#10981)
erichare Dec 11, 2025
7ba8c73
fix: Add authentication to various endpoints (#10977) (#10985)
erichare Dec 12, 2025
566a00a
Fix: ensure streamable-http session manager is entered and exited fro…
HzaRashid Dec 12, 2025
e3782ee
Fix: cuga integration (#10976) (#10990)
Adam-Aghili Dec 12, 2025
4f4cb2f
test(webhook): add comprehensive tests for webhook endpoint functiona…
Cristhianzl Dec 12, 2025
966c223
fix: Improve image path extraction and validation (#10999)
Cristhianzl Dec 12, 2025
0bfe12e
fix: make key generated on mcp json be shown, make placeholder show u…
lucaseduoli Dec 12, 2025
c067002
Fix: disable mcp sse endpoints astra (#11004)
HzaRashid Dec 12, 2025
3a92285
fix: mcp-proxy process leak (#11008)
Adam-Aghili Dec 12, 2025
16303cc
feat: add build-nightly-ep to docker-nightly-build (#10942)
Adam-Aghili Dec 13, 2025
5160138
fix(message.py): simplify file presence check using kwargs.get() for …
Cristhianzl Dec 14, 2025
5227f61
fix: Disable Local storage option in Write File component for cloud e…
HimavarshaVS Dec 15, 2025
79938ae
fix: cuga update (#11019) (#11026)
Adam-Aghili Dec 15, 2025
8b085de
fix: langwatch traces all api endpoints (#11014)
HzaRashid Dec 15, 2025
9d44f3d
Fix: improve exception handling and status code for disabled endpoint…
HzaRashid Dec 15, 2025
ec09b2b
fix(deps): Pin langchain-mcp-adapters to resolve langchain-core compa…
Cristhianzl Dec 16, 2025
0236d9a
fix: Make sure the research translation loop template is properly ope…
erichare Dec 16, 2025
072e94e
fix: disable knowledge components in astra (#11047)
HzaRashid Dec 16, 2025
2140a7f
fix: Advanced mode in read file component (#11041) (#11056)
HimavarshaVS Dec 17, 2025
be0c024
fix: validate flow file_save path is in a valid location (#11060)
jordanrfrazier Dec 17, 2025
d435761
refactor: Move fetch credentials to the customizations (#11049) (#11063)
mfortman11 Dec 17, 2025
cd90e87
fix: Fix async context handling in serve command and add integration …
ogabrielluiz Dec 17, 2025
b884fe4
refactor(tests): rename test IDs from helpersCreate List and logicPas…
Cristhianzl Dec 17, 2025
197b5af
refactor: Langflow cloud updates (#10910)
mfortman11 Dec 17, 2025
b26d032
fix: release workflow (#11087)
Adam-Aghili Dec 17, 2025
b6a5fb0
Revert "fix: release workflow " (#11088)
Adam-Aghili Dec 17, 2025
8b0f727
fix: release workflow (#11089)
Adam-Aghili Dec 18, 2025
783aba7
fix: use langflow package path for database location (#11107)
ogabrielluiz Dec 19, 2025
f4ed889
chore: bump versions for langflow 1.7.1, langflow-base 0.7.1, and lfx…
ogabrielluiz Dec 19, 2025
47ce5bf
test(regression): update test to use box selection for Combine Text n…
Cristhianzl Dec 19, 2025
6aa9178
test(fileUploadComponent.spec.ts): increase timeout duration for file…
Cristhianzl Dec 19, 2025
1796285
Merge branch 'release-1.7.1' into aka-merge-1.7.1
Adam-Aghili Dec 22, 2025
998244d
Merge branch 'main' into aka-merge-1.7.1
erichare Dec 22, 2025
b3b700f
Template update
erichare Dec 22, 2025
1e6f648
Optimize get_cache_service
codeflash-ai[bot] Dec 22, 2025
8d9080a
[autofix.ci] apply automated fixes
autofix-ci[bot] Dec 22, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/nightly_build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,7 @@ jobs:
with:
build_docker_base: true
build_docker_main: true
build_docker_ep: true
build_lfx: true
nightly_tag_main: ${{ needs.create-nightly-tag.outputs.main_tag }}
nightly_tag_base: ${{ needs.create-nightly-tag.outputs.base_tag }}
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -385,7 +385,7 @@ jobs:

build-lfx:
name: Build LFX
needs: [ci]
needs: [build-base]
if: ${{ inputs.release_lfx }}
runs-on: ubuntu-latest
outputs:
Expand Down Expand Up @@ -434,7 +434,7 @@ jobs:

test-cross-platform:
name: Test Cross-Platform Installation
needs: [build-base, build-main]
needs: [build-base, build-main, build-lfx]
if: |
always() &&
!cancelled() &&
Expand Down Expand Up @@ -494,7 +494,7 @@ jobs:
publish-lfx:
name: Publish LFX to PyPI
if: ${{ inputs.release_lfx }}
needs: [build-lfx]
needs: [build-lfx, test-cross-platform]
runs-on: ubuntu-latest
steps:
- name: Download LFX artifact
Expand Down
4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "langflow"
version = "1.7.0"
version = "1.7.1"
description = "A Python package with a built-in web application"
requires-python = ">=3.10,<3.14"
license = "MIT"
Expand All @@ -17,7 +17,7 @@ maintainers = [
]
# Define your main dependencies here
dependencies = [
"langflow-base~=0.7.0",
"langflow-base~=0.7.1",
"beautifulsoup4==4.12.3",
"google-search-results>=2.4.1,<3.0.0",
"google-api-python-client==2.154.0",
Expand Down
2 changes: 1 addition & 1 deletion src/backend/base/langflow/api/utils/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ def remove_api_keys(flow: dict):
node_data = node.get("data").get("node")
template = node_data.get("template")
for value in template.values():
if isinstance(value, dict) and has_api_terms(value["name"]) and value.get("password"):
if isinstance(value, dict) and "name" in value and has_api_terms(value["name"]) and value.get("password"):
value["value"] = None

return flow
Comment on lines 61 to 67

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 HIGH - Missing None check before dictionary access
Category: bug

Description:
In the remove_api_keys function, the code accesses node.get('data').get('node') without checking if node.get('data') returns None. This can cause an AttributeError at runtime.

Suggestion:
Add proper None checks: node_data = node.get('data'); if node_data: node_data = node_data.get('node')

Confidence: 95%
Rule: bug_null_pointer_python

Expand Down
21 changes: 17 additions & 4 deletions src/backend/base/langflow/api/v1/monitor.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,11 @@
from langflow.api.utils import DbSession, custom_params
from langflow.schema.message import MessageResponse
from langflow.services.auth.utils import get_current_active_user
from langflow.services.database.models.flow.model import Flow
from langflow.services.database.models.message.model import MessageRead, MessageTable, MessageUpdate
from langflow.services.database.models.transactions.crud import transform_transaction_table
from langflow.services.database.models.transactions.model import TransactionTable
from langflow.services.database.models.user.model import User
from langflow.services.database.models.vertex_builds.crud import (
delete_vertex_builds_by_flow_id,
get_vertex_builds_by_flow_id,

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 HIGH - Missing authorization on vertex builds endpoint
Category: security

Description:
The GET /builds endpoint accepts a flow_id parameter but only checks authentication. It doesn't verify the user owns the flow before returning vertex builds.

Suggestion:
Add authorization check to verify flow ownership: check if flow.user_id == current_user.id before returning data.

Confidence: 90%
Rule: soc2_rbac_least_privilege

Expand All @@ -39,15 +41,20 @@ async def delete_vertex_builds(flow_id: Annotated[UUID, Query()], session: DbSes
raise HTTPException(status_code=500, detail=str(e)) from e


@router.get("/messages/sessions", dependencies=[Depends(get_current_active_user)])
@router.get("/messages/sessions")
async def get_message_sessions(
session: DbSession,
Comment on lines 41 to 46

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 HIGH - Missing authorization on vertex builds deletion
Category: security

Description:
The DELETE /builds endpoint accepts a flow_id parameter but only checks authentication. It doesn't verify the user owns the flow before deleting its vertex builds.

Suggestion:
Add authorization check to verify flow ownership before deletion.

Confidence: 90%
Rule: soc2_rbac_least_privilege

current_user: Annotated[User, Depends(get_current_active_user)],
flow_id: Annotated[UUID | None, Query()] = None,
) -> list[str]:
try:
stmt = select(MessageTable.session_id).distinct()
stmt = stmt.where(col(MessageTable.session_id).isnot(None))

# Filter by user's flows
user_flows_stmt = select(Flow.id).where(Flow.user_id == current_user.id)
stmt = stmt.where(col(MessageTable.flow_id).in_(user_flows_stmt))

if flow_id:
stmt = stmt.where(MessageTable.flow_id == flow_id)

Expand All @@ -57,9 +64,10 @@ async def get_message_sessions(
raise HTTPException(status_code=500, detail=str(e)) from e


@router.get("/messages", dependencies=[Depends(get_current_active_user)])
@router.get("/messages")
async def get_messages(
session: DbSession,
current_user: Annotated[User, Depends(get_current_active_user)],
flow_id: Annotated[UUID | None, Query()] = None,
session_id: Annotated[str | None, Query()] = None,
sender: Annotated[str | None, Query()] = None,
Expand All @@ -68,6 +76,11 @@ async def get_messages(
) -> list[MessageResponse]:
try:
stmt = select(MessageTable)

# Filter by user's flows
user_flows_stmt = select(Flow.id).where(Flow.user_id == current_user.id)
stmt = stmt.where(col(MessageTable.flow_id).in_(user_flows_stmt))

if flow_id:
stmt = stmt.where(MessageTable.flow_id == flow_id)
if session_id:
Expand All @@ -80,8 +93,8 @@ async def get_messages(
if sender_name:
stmt = stmt.where(MessageTable.sender_name == sender_name)
if order_by:
col = getattr(MessageTable, order_by).asc()
stmt = stmt.order_by(col)
order_col = getattr(MessageTable, order_by).asc()

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 MEDIUM - Dynamic attribute access without validation
Category: bug

Description:
getattr(MessageTable, order_by) could raise AttributeError if order_by contains invalid attribute name. User-supplied parameter not validated.

Suggestion:
Add validation to ensure order_by is a valid MessageTable attribute before calling getattr, or use a whitelist of allowed values.

Confidence: 80%
Rule: bug_null_pointer

Comment on lines 95 to +96

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 HIGH - Unsafe dynamic attribute access allows arbitrary column access
Category: security

Description:
The order_by parameter is used directly with getattr(MessageTable, order_by) without validation. An attacker can specify any attribute name, potentially accessing internal methods or causing AttributeError exceptions.

Suggestion:
Implement a whitelist validation for the order_by parameter. Only allow specific, approved column names like 'timestamp', 'sender', 'sender_name', 'session_id', 'created_at'.

Confidence: 90%
Rule: py_add_input_validation_for_critical_parame

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 LOW - Dynamic column ordering needs validation
Category: security

Description:
Uses getattr(MessageTable, order_by) to dynamically construct ORDER BY clause from user input. While exploitability is limited by SQLAlchemy ORM, explicit allowlist is better.

Suggestion:
Add explicit validation: ALLOWED_ORDER_COLUMNS = {'timestamp', 'sender', 'sender_name'}

Confidence: 65%
Rule: python_sql_string_formatting

stmt = stmt.order_by(order_col)
messages = await session.exec(stmt)
return [MessageResponse.model_validate(d, from_attributes=True) for d in messages]
except Exception as e:

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 CRITICAL - Missing resource-level authorization on message deletion
Category: security

Description:
The DELETE /messages endpoint only checks authentication but doesn't verify users own the messages being deleted. An attacker could delete other users' messages.

Suggestion:
Add authorization logic to verify the current user owns the messages before deletion by checking if message.flow_id belongs to the user's flows.

Confidence: 95%
Rule: soc2_rbac_least_privilege

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 CRITICAL - Missing resource-level authorization on message update
Category: security

Description:
The PUT /messages/{message_id} endpoint only checks authentication but doesn't verify the user owns the message before updating it.

Suggestion:
Add authorization check to verify message ownership by checking if the message's flow_id belongs to a flow owned by current_user.

Confidence: 95%
Rule: soc2_rbac_least_privilege

Expand Down

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -1577,6 +1577,7 @@
"Message"
],
"beta": false,
"category": "models_and_agents",
"conditional_paths": [],
"custom_fields": {},
"description": "Extracts text using a template.",
Expand Down

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

Large diffs are not rendered by default.

8 changes: 6 additions & 2 deletions src/backend/base/langflow/memory.py
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,9 @@ async def aupdate_messages(messages: Message | list[Message]) -> list[Message]:
# Convert flow_id to UUID if it's a string preventing error when saving to database
if msg.flow_id and isinstance(msg.flow_id, str):
msg.flow_id = UUID(msg.flow_id)
session.add(msg)
result = session.add(msg)
if asyncio.iscoroutine(result):
await result
updated_messages.append(msg)
Comment on lines +168 to 170

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 MEDIUM - Conditional await for session.add()
Category: bug

Description:
Using asyncio.iscoroutine() to check if session.add() result needs awaiting. SQLAlchemy's session.add() is typically synchronous.

Suggestion:
SQLAlchemy's session.add() is synchronous and returns None. This check may be unnecessary defensive code. Verify if the SQLAlchemy version being used has async add().

Confidence: 70%
Rule: python_type_hints_missing

else:
error_message = f"Message with id {message.id} not found"
Expand All @@ -190,7 +192,9 @@ async def aadd_messagetables(messages: list[MessageTable], session: AsyncSession
try:
try:
for message in messages:
session.add(message)
result = session.add(message)
if asyncio.iscoroutine(result):
await result
await session.commit()
# This is a hack.
# We are doing this because build_public_tmp causes the CancelledError to be raised

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟠 HIGH - CancelledError caught and converted to ValueError with retry
Category: bug

Description:
asyncio.CancelledError is caught and leads to recursive retry or conversion to ValueError. CancelledError should propagate for proper task cleanup.

Suggestion:
Don't catch CancelledError for retry logic - it should propagate. The code comment mentions this is a 'hack' for build_public_tmp. Consider a proper fix rather than catching CancelledError.

Confidence: 90%
Rule: py_re_raise_systemexit_and_keyboardinterrup

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 MEDIUM - Second CancelledError handler converts to ValueError
Category: bug

Description:
Another CancelledError handler converts the exception to ValueError. AsyncIO cancellation should propagate as-is for proper task cleanup.

Suggestion:
Re-raise asyncio.CancelledError directly instead of wrapping it in ValueError.

Confidence: 85%
Rule: py_re_raise_systemexit_and_keyboardinterrup

Expand Down
11 changes: 7 additions & 4 deletions src/backend/base/langflow/services/deps.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from __future__ import annotations

from contextlib import asynccontextmanager
from typing import TYPE_CHECKING
from typing import TYPE_CHECKING, Union

from langflow.services.schema import ServiceType

Expand Down Expand Up @@ -176,15 +176,18 @@
yield session


def get_cache_service() -> CacheService | AsyncBaseCacheService:
def get_cache_service() -> Union[CacheService, AsyncBaseCacheService]: # noqa: UP007
"""Retrieves the cache service from the service manager.
Returns:
The cache service instance.
"""
from langflow.services.cache.factory import CacheServiceFactory
if not hasattr(get_cache_service, "_factory"):
from langflow.services.cache.factory import CacheServiceFactory

return get_service(ServiceType.CACHE_SERVICE, CacheServiceFactory())
get_cache_service._factory = CacheServiceFactory()

Check failure on line 188 in src/backend/base/langflow/services/deps.py

View workflow job for this annotation

GitHub Actions / Ruff Style Check (3.13)

Ruff (SLF001)

src/backend/base/langflow/services/deps.py:188:9: SLF001 Private member accessed: `_factory`

return get_service(ServiceType.CACHE_SERVICE, get_cache_service._factory)

Check failure on line 190 in src/backend/base/langflow/services/deps.py

View workflow job for this annotation

GitHub Actions / Ruff Style Check (3.13)

Ruff (SLF001)

src/backend/base/langflow/services/deps.py:190:51: SLF001 Private member accessed: `_factory`
Comment on lines +179 to +190

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 MEDIUM - Function attribute used for caching
Category: quality

Description:
The function get_cache_service uses a mutable function attribute _factory for caching. This is not a standard Python pattern and could lead to unexpected behavior in multi-threaded environments.

Suggestion:
Consider using @lru_cache decorator or a proper singleton pattern instead of function attributes for caching

Confidence: 65%
Rule: python_class_attribute_mutable

Comment on lines +185 to +190

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 LOW - Function attribute used for caching without documentation
Category: quality

Description:
Using function attributes (_factory) for caching is non-standard. While functional, it could cause confusion or thread-safety issues.

Suggestion:
Add a comment explaining the caching pattern, or use functools.lru_cache or a module-level variable for cleaner implementation.

Confidence: 70%
Rule: qual_semantic_naming_python



def get_shared_component_cache_service() -> CacheService:
Expand Down
6 changes: 5 additions & 1 deletion src/backend/base/langflow/services/storage/s3.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
"""S3-based storage service implementation using async boto3."""
"""S3-based storage service implementation using async boto3.

This service handles file storage operations with AWS S3, including
file upload, download, deletion, and listing operations.
"""

from __future__ import annotations

Expand Down
4 changes: 2 additions & 2 deletions src/backend/base/pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "langflow-base"
version = "0.7.0"
version = "0.7.1"
description = "A Python package with a built-in web application"
requires-python = ">=3.10,<3.14"
license = "MIT"
Expand All @@ -17,7 +17,7 @@ maintainers = [
]

dependencies = [
"lfx~=0.2.0",
"lfx~=0.2.1",
"fastapi>=0.115.2,<1.0.0",
"httpx[http2]>=0.27,<1.0.0",
"aiofile>=3.9.0,<4.0.0",
Expand Down
128 changes: 127 additions & 1 deletion src/backend/tests/unit/api/test_api_utils.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from unittest.mock import patch

from langflow.api.utils import get_suggestion_message
from langflow.api.utils import get_suggestion_message, remove_api_keys
from langflow.services.database.models.flow.utils import get_outdated_components
from langflow.utils.version import get_version_info

Expand Down Expand Up @@ -43,3 +43,129 @@ def test_get_outdated_components():
result = get_outdated_components(flow)
# Assert the result is as expected
assert result == expected_outdated_components


def test_remove_api_keys():
"""Test that remove_api_keys properly removes API keys and handles various template structures.

This test validates the fix for the bug where remove_api_keys would crash when
encountering template values without 'name' keys (e.g., Note components with
only backgroundColor).
"""
# Test case 1: Flow with API key that should be removed
flow_with_api_key = {
"data": {
"nodes": [
{
"data": {
"node": {
"template": {
"api_key": {
"name": "api_key",
"value": "secret-123",
"password": True,
},
"openai_api_key": {
"name": "openai_api_key",
"value": "sk-abc123",
"password": True,
},
}
}
}
}
]
}
}

result = remove_api_keys(flow_with_api_key)
assert result["data"]["nodes"][0]["data"]["node"]["template"]["api_key"]["value"] is None
assert result["data"]["nodes"][0]["data"]["node"]["template"]["openai_api_key"]["value"] is None

# Test case 2: Flow with Note component (no 'name' key) - this is the bug fix
flow_with_note = {
"data": {
"nodes": [
{
"data": {
"node": {
"template": {
"backgroundColor": {"value": "#ffffff"}, # No 'name' key
"text": {"value": "Test note"}, # No 'name' key
}
}
}
}
]
}
}

# This should not raise an error (the bug that was fixed)
result = remove_api_keys(flow_with_note)
# Values should be preserved since they're not API keys
assert result["data"]["nodes"][0]["data"]["node"]["template"]["backgroundColor"]["value"] == "#ffffff"
assert result["data"]["nodes"][0]["data"]["node"]["template"]["text"]["value"] == "Test note"

# Test case 3: Mixed flow with both API keys and template values without 'name'
mixed_flow = {
"data": {
"nodes": [
{
"data": {
"node": {
"template": {
"backgroundColor": {"value": "#ffffff"}, # No 'name' key
"api_token": {
"name": "api_token",
"value": "token-xyz",
"password": True,
},
"regular_field": {
"name": "regular_field",
"value": "keep-this",
},
}
}
}
}
]
}
}

result = remove_api_keys(mixed_flow)
# backgroundColor should be preserved (no 'name' key)
assert result["data"]["nodes"][0]["data"]["node"]["template"]["backgroundColor"]["value"] == "#ffffff"
# API token should be removed
assert result["data"]["nodes"][0]["data"]["node"]["template"]["api_token"]["value"] is None
# Regular field should be kept
assert result["data"]["nodes"][0]["data"]["node"]["template"]["regular_field"]["value"] == "keep-this"

# Test case 4: Flow with auth_token (password field but not password=True)
flow_with_non_password_api = {
"data": {
"nodes": [
{
"data": {
"node": {
"template": {
"api_key": {
"name": "api_key",
"value": "should-not-be-removed",
"password": False, # Not a password field
},
}
}
}
}
]
}
}

result = remove_api_keys(flow_with_non_password_api)
# Should NOT be removed because password is False
assert result["data"]["nodes"][0]["data"]["node"]["template"]["api_key"]["value"] == "should-not-be-removed"

# Test case 5: Empty flow
empty_flow = {"data": {"nodes": []}}
result = remove_api_keys(empty_flow)
assert result == empty_flow
Loading
Loading