Skip to content

fix: protect URL components from SSRF#13643

Merged
erichare merged 3 commits into
release-1.10.1from
fix/pvr0781399-ssrf-components
Jun 15, 2026
Merged

fix: protect URL components from SSRF#13643
erichare merged 3 commits into
release-1.10.1from
fix/pvr0781399-ssrf-components

Conversation

@erichare

@erichare erichare commented Jun 12, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Add shared SSRF-safe httpx helpers plus sync DNS pinning transport support for SDK clients.
  • Route the reported user-controlled URL fields through SSRF validation and pinned clients for LM Studio, Home Assistant, DeepSeek, xAI, Glean, Hugging Face, Ollama, and LiteLLM.
  • Add regression coverage for PVR0781399 / H1-3783851 to prove metadata URLs are blocked before network or SDK client construction.

Validation

  • uv run ruff check src/lfx/src/lfx/utils/ssrf_httpx.py src/lfx/src/lfx/utils/ssrf_transport.py src/lfx/src/lfx/components/deepseek/deepseek.py src/lfx/src/lfx/components/glean/glean_search_api.py src/lfx/src/lfx/components/homeassistant/home_assistant_control.py src/lfx/src/lfx/components/homeassistant/list_home_assistant_states.py src/lfx/src/lfx/components/huggingface/huggingface_inference_api.py src/lfx/src/lfx/components/litellm/litellm_proxy.py src/lfx/src/lfx/components/lmstudio/lmstudioembeddings.py src/lfx/src/lfx/components/lmstudio/lmstudiomodel.py src/lfx/src/lfx/components/ollama/ollama.py src/lfx/src/lfx/components/ollama/ollama_embeddings.py src/lfx/src/lfx/components/xai/xai.py src/backend/tests/unit/components/test_ssrf_guarded_url_components.py src/lfx/tests/unit/utils/test_ssrf_httpx.py src/backend/tests/unit/components/languagemodels/test_deepseek.py src/backend/tests/unit/components/languagemodels/test_litellm_proxy.py src/backend/tests/unit/components/languagemodels/test_chatollama_component.py src/backend/tests/unit/components/embeddings/test_ollama_embeddings_component.py
  • uv run pytest src/backend/tests/unit/components/test_ssrf_guarded_url_components.py src/backend/tests/unit/components/languagemodels/test_deepseek.py src/backend/tests/unit/components/languagemodels/test_litellm_proxy.py src/backend/tests/unit/components/languagemodels/test_chatollama_component.py src/backend/tests/unit/components/embeddings/test_ollama_embeddings_component.py
  • uv run pytest tests/unit/utils/test_ssrf_httpx.py from src/lfx after isolated uv sync

Notes

  • Local endpoints such as Ollama and LM Studio continue to use the existing LANGFLOW_SSRF_ALLOWED_HOSTS path when SSRF protection is enabled.

Summary by CodeRabbit

  • Bug Fixes

    • Implemented SSRF (Server-Side Request Forgery) protection across multiple components to prevent unauthorized access to internal network resources.
  • Tests

    • Added comprehensive test coverage for SSRF protection scenarios and updated existing tests accordingly.

@coderabbitai

coderabbitai Bot commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Important

Review skipped

Auto incremental reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: fd2bab6f-919d-4d06-8275-f784d3d30008

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Walkthrough

This PR adds comprehensive SSRF (Server-Side Request Forgery) protection to Langflow's outbound HTTP requests. The implementation includes DNS-pinning transport for both sync and async HTTP clients, SSRF-safe request wrappers, and migration of 10+ LLM and API components to use protected helpers instead of requests or direct httpx calls. All existing and new tests verify protection against metadata-service attacks.

Changes

SSRF Protection Implementation and Component Migration

Layer / File(s) Summary
SSRF Core Utilities: HTTP Helpers and Transport
src/lfx/src/lfx/utils/ssrf_httpx.py, src/lfx/src/lfx/utils/ssrf_transport.py, src/lfx/tests/unit/utils/test_ssrf_httpx.py
HTTP-level URL validation (validate_url_for_ssrf_or_raise), redirect-blocking guard (_raise_if_following_redirects), sync/async client factories with DNS pinning, and shared kwarg builders for OpenAI-compatible clients. New DNSPinningSyncNetworkBackend and SSRFProtectedSyncTransport add sync-mode DNS pinning. Tests verify metadata-IP blocking and DNS rebinding prevention.
LLM and API Component Migration to SSRF-Safe HTTP
src/lfx/src/lfx/components/deepseek/deepseek.py, src/lfx/src/lfx/components/glean/glean_search_api.py, src/lfx/src/lfx/components/homeassistant/home_assistant_control.py, src/lfx/src/lfx/components/homeassistant/list_home_assistant_states.py, src/lfx/src/lfx/components/huggingface/huggingface_inference_api.py, src/lfx/src/lfx/components/litellm/litellm_proxy.py, src/lfx/src/lfx/components/lmstudio/lmstudioembeddings.py, src/lfx/src/lfx/components/lmstudio/lmstudiomodel.py, src/lfx/src/lfx/components/ollama/ollama.py, src/lfx/src/lfx/components/ollama/ollama_embeddings.py, src/lfx/src/lfx/components/xai/xai.py
All components switch HTTP calls from requests or direct httpx to SSRF-safe helpers (ssrf_safe_httpx_get, ssrf_safe_httpx_post, ssrf_safe_async_get, ssrf_safe_async_post). URL validation added where needed. OpenAI-compatible clients configured via ssrf_protected_openai_clients_for_url to inject SSRF-protected transports. Exception handling standardized to catch SSRFProtectionError separately from general network errors.
Test Updates: Fixtures and Comprehensive SSRF Protection Verification
src/backend/tests/unit/components/embeddings/test_ollama_embeddings_component.py, src/backend/tests/unit/components/languagemodels/test_chatollama_component.py, src/backend/tests/unit/components/languagemodels/test_deepseek.py, src/backend/tests/unit/components/languagemodels/test_litellm_proxy.py, src/backend/tests/unit/components/test_ssrf_guarded_url_components.py
Existing tests updated with autouse fixtures disabling SSRF protection and mock targets changed to new SSRF-safe helpers. New comprehensive test module verifies SSRF blocks metadata-service URLs (e.g., 169.254.169.254) across all migrated components, confirming error messages and preventing underlying HTTP/SDK calls.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • langflow-ai/langflow#13572: Complements SSRF redirect protection with manual per-hop URL revalidation for URL components that follow redirects.

Suggested labels

bug

Suggested reviewers

  • Adam-Aghili
  • Cristhianzl
🚥 Pre-merge checks | ✅ 8 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 30.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (8 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title 'fix: protect URL components from SSRF' clearly and concisely summarizes the main change—adding SSRF protection to URL-based components. It is specific, directly related to the core objective, and follows conventional commit messaging.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Test Coverage For New Implementations ✅ Passed PR adds SSRF unit tests (test_ssrf_guarded_url_components.py, test_ssrf_httpx.py) and updates component tests to mock SSRF-safe HTTP calls; assertions verify blocked URLs prevent httpx/SDK client c...
Test Quality And Coverage ✅ Passed New tests validate SSRF helper blocking (internal IP) and DNS pinning, and regression suite asserts multiple components block metadata URLs before any HTTP/SDK calls; async tests use pytest-marked...
Test File Naming And Structure ✅ Passed Added/updated tests are properly named backend test_*.py, use clear descriptive test_* functions, logical fixtures/autouse env setup, mark async with pytest.mark.asyncio, and include both SSR...
Excessive Mock Usage Warning ✅ Passed Mock/patch usage in the touched tests is focused on external dependencies (httpx/SDK clients) to prevent real network calls; there’s no clear evidence mocks obscure core logic beyond typical unit p...

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/pvr0781399-ssrf-components

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions github-actions Bot added the bug Something isn't working label Jun 12, 2026
@github-actions

github-actions Bot commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

✅ Test Coverage Advisor

No source changes detected without accompanying tests. Thanks for keeping coverage up! 🎉

Advisory check only — never blocks merge.

@erichare erichare requested a review from Adam-Aghili June 12, 2026 22:10
@github-actions github-actions Bot added bug Something isn't working and removed bug Something isn't working labels Jun 12, 2026
@github-actions github-actions Bot added bug Something isn't working and removed bug Something isn't working labels Jun 12, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick comments (9)
src/lfx/src/lfx/utils/ssrf_httpx.py (1)

78-86: 📐 Maintainability & Code Quality | 💤 Low value

Document caller responsibility for closing returned clients.

ssrf_protected_openai_clients_for_url returns httpx.Client and httpx.AsyncClient instances that are not context-managed here. The caller (typically LangChain/OpenAI SDK components) is responsible for their lifecycle. Consider adding a docstring note clarifying that these clients should be closed when no longer needed, or that they're intended to be passed to SDK classes that manage their lifecycle.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lfx/src/lfx/utils/ssrf_httpx.py` around lines 78 - 86, Update the
docstring for ssrf_protected_openai_clients_for_url to explicitly state that it
returns new httpx.Client and httpx.AsyncClient instances which are not
context-managed by this function and therefore the caller is responsible for
closing them (or passing them to SDK/LangChain components that will manage their
lifecycle); mention the expected usage pattern (pass to OpenAI/LangChain
constructors or close() / aclose() when done) so callers know to manage the
clients' lifecycle.
src/backend/tests/unit/components/embeddings/test_ollama_embeddings_component.py (1)

923-925: 📐 Maintainability & Code Quality | 💤 Low value

Consider moving the fixture to the top of the class for readability.

The autouse fixture works correctly regardless of position, but placing it near the other fixtures (lines 24-42) would improve discoverability and follow the common convention of grouping fixtures together.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@src/backend/tests/unit/components/embeddings/test_ollama_embeddings_component.py`
around lines 923 - 925, Move the autouse fixture disable_ssrf_protection into
the top of the test class so it sits alongside the other fixtures for that class
(near where the other fixtures are declared) to improve readability and
discoverability; keep the monkeypatch.setenv("LANGFLOW_SSRF_PROTECTION_ENABLED",
"false") behavior and retain the `@pytest.fixture`(autouse=True) decorator and the
same function name disable_ssrf_protection and signature so tests continue to
run unchanged.
src/backend/tests/unit/components/languagemodels/test_litellm_proxy.py (2)

242-244: 📐 Maintainability & Code Quality | 💤 Low value

Consider moving the fixture to the top of the class for readability.

Same pattern as other test files - placing this fixture near the other fixtures would improve discoverability.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/backend/tests/unit/components/languagemodels/test_litellm_proxy.py`
around lines 242 - 244, Move the autouse fixture disable_ssrf_protection to the
top of the test class (near the other fixtures) so it’s easier to discover;
specifically relocate the pytest.fixture(autouse=True) def
disable_ssrf_protection(self, monkeypatch):
monkeypatch.setenv("LANGFLOW_SSRF_PROTECTION_ENABLED", "false") block to
immediately follow the class declaration or the existing fixtures in the class
so it appears with the other setup fixtures.

84-94: 📐 Maintainability & Code Quality | ⚡ Quick win

Test assertion doesn't verify SSRF client kwargs are passed to ChatOpenAI.

The component now passes **ssrf_client_kwargs to ChatOpenAI (line 90 in the component), but this test's assertion on lines 84-93 doesn't account for those additional kwargs. When SSRF protection is disabled, ssrf_protected_openai_clients_for_url likely returns an empty dict, so the test passes. However, this means the test doesn't verify the SSRF integration path.

Consider adding a separate test case that enables SSRF protection and verifies the client kwargs are correctly passed through.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/backend/tests/unit/components/languagemodels/test_litellm_proxy.py`
around lines 84 - 94, The current test only asserts ChatOpenAI was called
without SSRF kwargs; add a new unit test that enables SSRF protection (mock
ssrf_protected_openai_clients_for_url to return a non-empty dict) and ensure the
component instantiates ChatOpenAI with those extra kwargs: assert
mock_chat_openai.assert_called_once_with(...) includes the returned
ssrf_client_kwargs along with existing params (base_url, api_key, model,
temperature, max_tokens, timeout, max_retries, streaming) and that the function
under test (the Litellm proxy constructor/creator that calls ChatOpenAI) returns
the mock instance; locate the call site by looking for ChatOpenAI and
ssrf_protected_openai_clients_for_url in test_litellm_proxy.py and mirror the
existing test pattern but with SSRF enabled.
src/backend/tests/unit/components/languagemodels/test_chatollama_component.py (1)

1264-1266: 📐 Maintainability & Code Quality | 💤 Low value

Consider moving the fixture to the top of the class for readability.

Same as the Ollama embeddings test file - placing this fixture near the other fixtures (lines 14-50) would improve discoverability.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@src/backend/tests/unit/components/languagemodels/test_chatollama_component.py`
around lines 1264 - 1266, Move the autouse fixture disable_ssrf_protection into
the test class's fixture section so it's colocated with the other fixtures for
readability; specifically, relocate the def disable_ssrf_protection(self,
monkeypatch) fixture (which sets LANGFLOW_SSRF_PROTECTION_ENABLED to "false") up
into the top of the class where the other fixtures are declared so it's easy to
find and consistent with the Ollama embeddings tests.
src/lfx/src/lfx/components/ollama/ollama_embeddings.py (2)

152-168: 📐 Maintainability & Code Quality | 💤 Low value

Unnecessary coroutine checks on synchronous httpx.Response.json().

httpx.Response.json() is a synchronous method that returns the parsed dictionary directly, not a coroutine. The asyncio.iscoroutine() checks on lines 153-154 and 167-168 are unnecessary.

♻️ Proposed simplification
             tags_response = await ssrf_safe_async_get(tags_url, headers=headers)
             tags_response.raise_for_status()
             models = tags_response.json()
-            if asyncio.iscoroutine(models):
-                models = await models
             await logger.adebug(f"Available models: {models}")
             # ...
                 show_response = await ssrf_safe_async_post(show_url, json=payload, headers=headers)
                 show_response.raise_for_status()
                 json_data = show_response.json()
-                if asyncio.iscoroutine(json_data):
-                    json_data = await json_data
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lfx/src/lfx/components/ollama/ollama_embeddings.py` around lines 152 -
168, Remove the unnecessary asyncio.iscoroutine checks and accompanying awaits
around httpx Response.json() calls in ollama_embeddings.py: eliminate the
iscoroutine checks for models = tags_response.json() and json_data =
show_response.json() and do not await their results (they are synchronous),
leaving models and json_data as the direct parsed dicts; update any logging/flow
that assumed awaiting (references: variables/models and show_response.json()
usage in the model filtering loop inside the Ollama embeddings logic).

77-112: 📐 Maintainability & Code Quality

Ollama embeddings: drop redundant SSRF-validation suggestion; minor cleanup for asyncio.iscoroutine(response.json())

  • The SSRF concern isn’t applicable: ssrf_protected_httpx_client_kwargs_for_url() already validates via validate_and_resolve_url() and converts SSRFProtectionError into a ValueError with the "SSRF Protection: ..." prefix.
  • asyncio.iscoroutine() checks around tags_response.json() / show_response.json() are unnecessary (httpx Response.json() is synchronous); simplifying would improve clarity.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lfx/src/lfx/components/ollama/ollama_embeddings.py` around lines 77 -
112, Remove the redundant SSRF-validation guidance and unnecessary coroutine
checks: rely on ssrf_protected_httpx_client_kwargs_for_url (which uses
validate_and_resolve_url and already raises ValueError for SSRF) instead of
suggesting extra SSRF handling, and simplify the response JSON handling by
deleting any asyncio.iscoroutine(...) guards around tags_response.json() and
show_response.json() and calling Response.json() directly (httpx Response.json()
is synchronous); update related error/exception paths accordingly in
build_embeddings and any helper functions that currently include those checks.
src/backend/tests/unit/components/test_ssrf_guarded_url_components.py (1)

26-27: 📐 Maintainability & Code Quality | 💤 Low value

Unnecessary @pytest.mark.asyncio decorator.

Per project configuration (asyncio_mode = 'auto' in pyproject.toml), async tests are auto-detected. The decorator can be removed.

Based on learnings: "pytest-asyncio is configured with asyncio_mode = 'auto' in pyproject.toml. This means you do not need to decorate test functions or classes with pytest.mark.asyncio."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/backend/tests/unit/components/test_ssrf_guarded_url_components.py` around
lines 26 - 27, Remove the unnecessary pytest.mark.asyncio decorator above the
async test function
test_lmstudio_model_update_blocks_metadata_url_before_httpx(); because
asyncio_mode is set to 'auto' the test framework will auto-detect async tests,
so delete the line with `@pytest.mark.asyncio` and leave the async def unchanged.

Source: Learnings

src/backend/tests/unit/components/languagemodels/test_deepseek.py (1)

115-118: 📐 Maintainability & Code Quality | 💤 Low value

Move fixture before test functions for better readability.

The disable_ssrf_protection fixture is defined after all test functions. While pytest collects fixtures regardless of position, placing fixtures at the top of the file (after imports) is the conventional pattern and improves readability.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/backend/tests/unit/components/languagemodels/test_deepseek.py` around
lines 115 - 118, Move the autouse fixture definition disable_ssrf_protection so
it appears immediately after the module imports and before any test_...
functions in test_deepseek.py; locate the
monkeypatch.setenv("LANGFLOW_SSRF_PROTECTION_ENABLED", "false") line inside the
disable_ssrf_protection fixture and cut/paste the entire
`@pytest.fixture`(autouse=True) def disable_ssrf_protection(...) block to the top
of the file (after imports) to follow pytest conventions and improve readability
while keeping its behavior unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In
`@src/backend/tests/unit/components/embeddings/test_ollama_embeddings_component.py`:
- Around line 923-925: Move the autouse fixture disable_ssrf_protection into the
top of the test class so it sits alongside the other fixtures for that class
(near where the other fixtures are declared) to improve readability and
discoverability; keep the monkeypatch.setenv("LANGFLOW_SSRF_PROTECTION_ENABLED",
"false") behavior and retain the `@pytest.fixture`(autouse=True) decorator and the
same function name disable_ssrf_protection and signature so tests continue to
run unchanged.

In
`@src/backend/tests/unit/components/languagemodels/test_chatollama_component.py`:
- Around line 1264-1266: Move the autouse fixture disable_ssrf_protection into
the test class's fixture section so it's colocated with the other fixtures for
readability; specifically, relocate the def disable_ssrf_protection(self,
monkeypatch) fixture (which sets LANGFLOW_SSRF_PROTECTION_ENABLED to "false") up
into the top of the class where the other fixtures are declared so it's easy to
find and consistent with the Ollama embeddings tests.

In `@src/backend/tests/unit/components/languagemodels/test_deepseek.py`:
- Around line 115-118: Move the autouse fixture definition
disable_ssrf_protection so it appears immediately after the module imports and
before any test_... functions in test_deepseek.py; locate the
monkeypatch.setenv("LANGFLOW_SSRF_PROTECTION_ENABLED", "false") line inside the
disable_ssrf_protection fixture and cut/paste the entire
`@pytest.fixture`(autouse=True) def disable_ssrf_protection(...) block to the top
of the file (after imports) to follow pytest conventions and improve readability
while keeping its behavior unchanged.

In `@src/backend/tests/unit/components/languagemodels/test_litellm_proxy.py`:
- Around line 242-244: Move the autouse fixture disable_ssrf_protection to the
top of the test class (near the other fixtures) so it’s easier to discover;
specifically relocate the pytest.fixture(autouse=True) def
disable_ssrf_protection(self, monkeypatch):
monkeypatch.setenv("LANGFLOW_SSRF_PROTECTION_ENABLED", "false") block to
immediately follow the class declaration or the existing fixtures in the class
so it appears with the other setup fixtures.
- Around line 84-94: The current test only asserts ChatOpenAI was called without
SSRF kwargs; add a new unit test that enables SSRF protection (mock
ssrf_protected_openai_clients_for_url to return a non-empty dict) and ensure the
component instantiates ChatOpenAI with those extra kwargs: assert
mock_chat_openai.assert_called_once_with(...) includes the returned
ssrf_client_kwargs along with existing params (base_url, api_key, model,
temperature, max_tokens, timeout, max_retries, streaming) and that the function
under test (the Litellm proxy constructor/creator that calls ChatOpenAI) returns
the mock instance; locate the call site by looking for ChatOpenAI and
ssrf_protected_openai_clients_for_url in test_litellm_proxy.py and mirror the
existing test pattern but with SSRF enabled.

In `@src/backend/tests/unit/components/test_ssrf_guarded_url_components.py`:
- Around line 26-27: Remove the unnecessary pytest.mark.asyncio decorator above
the async test function
test_lmstudio_model_update_blocks_metadata_url_before_httpx(); because
asyncio_mode is set to 'auto' the test framework will auto-detect async tests,
so delete the line with `@pytest.mark.asyncio` and leave the async def unchanged.

In `@src/lfx/src/lfx/components/ollama/ollama_embeddings.py`:
- Around line 152-168: Remove the unnecessary asyncio.iscoroutine checks and
accompanying awaits around httpx Response.json() calls in ollama_embeddings.py:
eliminate the iscoroutine checks for models = tags_response.json() and json_data
= show_response.json() and do not await their results (they are synchronous),
leaving models and json_data as the direct parsed dicts; update any logging/flow
that assumed awaiting (references: variables/models and show_response.json()
usage in the model filtering loop inside the Ollama embeddings logic).
- Around line 77-112: Remove the redundant SSRF-validation guidance and
unnecessary coroutine checks: rely on ssrf_protected_httpx_client_kwargs_for_url
(which uses validate_and_resolve_url and already raises ValueError for SSRF)
instead of suggesting extra SSRF handling, and simplify the response JSON
handling by deleting any asyncio.iscoroutine(...) guards around
tags_response.json() and show_response.json() and calling Response.json()
directly (httpx Response.json() is synchronous); update related error/exception
paths accordingly in build_embeddings and any helper functions that currently
include those checks.

In `@src/lfx/src/lfx/utils/ssrf_httpx.py`:
- Around line 78-86: Update the docstring for
ssrf_protected_openai_clients_for_url to explicitly state that it returns new
httpx.Client and httpx.AsyncClient instances which are not context-managed by
this function and therefore the caller is responsible for closing them (or
passing them to SDK/LangChain components that will manage their lifecycle);
mention the expected usage pattern (pass to OpenAI/LangChain constructors or
close() / aclose() when done) so callers know to manage the clients' lifecycle.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: ac8ee590-66f9-4988-827f-3d4fbedd730a

📥 Commits

Reviewing files that changed from the base of the PR and between 3c7ab70 and 4b5f982.

📒 Files selected for processing (20)
  • src/backend/tests/unit/components/embeddings/test_ollama_embeddings_component.py
  • src/backend/tests/unit/components/languagemodels/test_chatollama_component.py
  • src/backend/tests/unit/components/languagemodels/test_deepseek.py
  • src/backend/tests/unit/components/languagemodels/test_litellm_proxy.py
  • src/backend/tests/unit/components/test_ssrf_guarded_url_components.py
  • src/lfx/src/lfx/_assets/component_index.json
  • src/lfx/src/lfx/components/deepseek/deepseek.py
  • src/lfx/src/lfx/components/glean/glean_search_api.py
  • src/lfx/src/lfx/components/homeassistant/home_assistant_control.py
  • src/lfx/src/lfx/components/homeassistant/list_home_assistant_states.py
  • src/lfx/src/lfx/components/huggingface/huggingface_inference_api.py
  • src/lfx/src/lfx/components/litellm/litellm_proxy.py
  • src/lfx/src/lfx/components/lmstudio/lmstudioembeddings.py
  • src/lfx/src/lfx/components/lmstudio/lmstudiomodel.py
  • src/lfx/src/lfx/components/ollama/ollama.py
  • src/lfx/src/lfx/components/ollama/ollama_embeddings.py
  • src/lfx/src/lfx/components/xai/xai.py
  • src/lfx/src/lfx/utils/ssrf_httpx.py
  • src/lfx/src/lfx/utils/ssrf_transport.py
  • src/lfx/tests/unit/utils/test_ssrf_httpx.py

@codecov

codecov Bot commented Jun 12, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 41.02564% with 69 lines in your changes missing coverage. Please review.
✅ Project coverage is 58.27%. Comparing base (d736672) to head (3cd7643).
⚠️ Report is 1 commits behind head on release-1.10.1.

Files with missing lines Patch % Lines
src/lfx/src/lfx/utils/ssrf_httpx.py 31.88% 44 Missing and 3 partials ⚠️
src/lfx/src/lfx/utils/ssrf_transport.py 54.16% 16 Missing and 6 partials ⚠️

❌ Your project check has failed because the head coverage (54.32%) is below the target coverage (60.00%). You can increase the head coverage or adjust the target coverage.

Additional details and impacted files

Impacted file tree graph

@@                Coverage Diff                 @@
##           release-1.10.1   #13643      +/-   ##
==================================================
- Coverage           58.54%   58.27%   -0.28%     
==================================================
  Files                2302     2303       +1     
  Lines              219191   219248      +57     
  Branches            31131    32890    +1759     
==================================================
- Hits               128322   127758     -564     
- Misses              89411    90022     +611     
- Partials             1458     1468      +10     
Flag Coverage Δ
backend 65.08% <ø> (-0.24%) ⬇️
frontend 57.52% <ø> (-0.35%) ⬇️
lfx 54.32% <41.02%> (-0.03%) ⬇️

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

Files with missing lines Coverage Δ
src/lfx/src/lfx/utils/ssrf_transport.py 57.28% <54.16%> (-2.72%) ⬇️
src/lfx/src/lfx/utils/ssrf_httpx.py 31.88% <31.88%> (ø)

... and 64 files with indirect coverage changes

🚀 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.

@github-actions

github-actions Bot commented Jun 12, 2026

Copy link
Copy Markdown
Contributor

Frontend Unit Test Coverage Report

Coverage Summary

Lines Statements Branches Functions
Coverage: 43%
43.28% (57622/133133) 69.21% (7828/11309) 41.49% (1291/3111)

Unit Test Results

Tests Skipped Failures Errors Time
4940 0 💤 0 ❌ 0 🔥 14m 0s ⏱️

@github-actions github-actions Bot added bug Something isn't working and removed bug Something isn't working labels Jun 12, 2026

@Cristhianzl Cristhianzl left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

⚠️ Important (preferably this PR)

I1 — HuggingFace inference path is validation-only (DNS-rebinding window stays open)

File: src/lfx/src/lfx/components/huggingface/huggingface_inference_api.py:86 (build_embeddings)
Issue: build_embeddings calls validate_url_for_ssrf_or_raise(api_url) and then constructs HuggingFaceInferenceAPIEmbeddings, whose actual inference HTTP calls go through the langchain_community SDK client — which is not given the pinned transport. The OpenAI-compatible components (Ollama, LiteLLM, xAI, DeepSeek, LMStudio) inject the pinned client via http_client / sync_client_kwargs / async_client_kwargs, so their real request is pinned. The HF inference path is validated once, then the SDK re-resolves DNS on its own request.
Why it matters: validate_and_resolve_url's docstring explicitly sells DNS-rebinding prevention as the reason DNS pinning exists. For HF, an attacker-controlled domain with TTL=0 (public at validation, internal at the SDK's request) is not stopped on the inference path. Direct internal IPs/hostnames are still blocked at validation, so this is a residual rebinding gap, not a full bypass.
Suggested fix: If HuggingFaceInferenceAPIEmbeddings accepts a custom httpx client, pass a pinned one via ssrf_protected_openai_clients_for_url / ssrf_protected_httpx_client_kwargs_for_url. If it does not, document the residual rebinding risk for SDK-only components in a Why: comment at the validation site and track a follow-up — so the next reader knows the gap is known, not overlooked.

Code reference
def build_embeddings(self) -> Embeddings:
    api_url = self.get_api_url()
    validate_url_for_ssrf_or_raise(api_url)   # point-in-time only; SDK call below is not pinned
    ...

I2 — Inconsistent error contract for the same SSRF event

Files: src/lfx/src/lfx/components/glean/glean_search_api.py:85, src/lfx/src/lfx/components/homeassistant/home_assistant_control.py:137, src/lfx/src/lfx/components/ollama/ollama.py:330
Issue: The same blocked-URL event surfaces three different ways: Glean lets SSRFProtectionError propagate raw; HomeAssistant catches it and returns a "Error: SSRF Protection: {e}" string; the model components wrap it into ValueError("SSRF Protection: ..."). The test suite encodes all three (pytest.raises(SSRFProtectionError) for Glean, string-match for HomeAssistant, pytest.raises(ValueError, match="SSRF Protection") elsewhere).
Why it matters: A caller (or the UI) cannot rely on one error type/shape to detect an SSRF block. Tool components returning a string means the failure is in-band with normal output and easy to miss programmatically.
Suggested fix: Standardize per component category: tool components return a structured error consistently, model/embedding components raise ValueError("SSRF Protection: …") consistently, and avoid leaking the raw SSRFProtectionError to callers (Glean). Document the chosen contract once near the helper.


💡 Recommended (can ship as a follow-up)

R1 — Per-call client + re-resolution inside loops

File: src/lfx/src/lfx/components/ollama/ollama.py:423 (get_models), src/lfx/src/lfx/utils/ssrf_httpx.py:54
Issue: ssrf_safe_async_get/post each validate, resolve DNS, and build a fresh one-shot client per call. In get_models, ssrf_safe_async_post(show_url, …) runs once per model in a loop, so N model entries = N validations + N DNS resolutions + N clients.
Why it matters: Extra latency and connection churn on a hot path; the same host is re-resolved repeatedly. Not a correctness/security problem (re-validation is safe), purely efficiency.
Suggested fix: For loop call sites, resolve/validate once and reuse a single pinned client (create_ssrf_protected_client) for the batch of requests to the same host.

R2 — Document the unpinned fallback path

File: src/lfx/src/lfx/utils/ssrf_httpx.py:34-46 (_async_client_for_url / _sync_client_for_url)
Issue: When protection is enabled but validated_ips is empty (an allowlisted host such as localhost for Ollama), these return a plain unpinned httpx.Client()/AsyncClient(). This is intended — allowlisted hosts don't need pinning — but it isn't obvious that the only way to reach the fallback with protection enabled is an allowlist hit.
Why it matters: A future change to validate_and_resolve_url that returns empty IPs in a non-allowlist case would silently downgrade to an unpinned request.
Suggested fix: Add a one-line Why: comment stating the empty-IP branch is the allowlist case, or assert the host is allowlisted before returning the plain client.

R3 — Component tests cover only the block path

File: src/backend/tests/unit/components/test_ssrf_guarded_url_components.py
Issue: Every component test uses BLOCKED_URL (metadata IP) and asserts the call is gated. There is no component-level test that a valid public URL is allowed through with the pinned transport actually applied.
Why it matters: If a helper regressed to a no-op (e.g. returned {} kwargs unconditionally), the block-only tests would still pass while protection silently disappeared on the allow path.
Suggested fix: Add one allow-path test per integration style (one OpenAI-compatible, one ssrf_safe_*) asserting a public URL succeeds and the pinned transport / kwargs are present on the constructed client.


🟢 Nice-to-have

N1 — Mock usage here is correct; call it out so it isn't flagged

Files: src/lfx/tests/unit/utils/test_ssrf_httpx.py, src/backend/tests/unit/components/test_ssrf_guarded_url_components.py
Langflow prefers real integrations, but DNS rebinding and "never reached the network" assertions cannot be reproduced with a real sandbox — this is the documented exception. The patch("socket.getaddrinfo") + assert_not_called() approach is the right tool. No change needed; a one-line note in the PR description that the mocks are the sanctioned exception would pre-empt a reviewer objection.

N2 — component_index.json regenerated

File: src/lfx/src/lfx/_assets/component_index.json
Same note as the Smart Router PR: confirm the committed hashes equal a clean index rebuild and that CI's sha256 integrity check covers the file. The .secrets.baseline churn (line-number + generated_at) is benign — no new secret introduced.

@github-actions github-actions Bot added the lgtm This PR has been approved by a maintainer label Jun 15, 2026
@erichare erichare added this pull request to the merge queue Jun 15, 2026
Merged via the queue into release-1.10.1 with commit a074efa Jun 15, 2026
124 of 125 checks passed
@erichare erichare deleted the fix/pvr0781399-ssrf-components branch June 15, 2026 18:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working lgtm This PR has been approved by a maintainer

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants