feat: add SSRF protection configuration for API Requests#10544
Conversation
|
Important Review skippedAuto incremental reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the You can disable this status message by setting the WalkthroughA new SSRF protection system is introduced with validation utilities, configuration settings, and integration into the API request component. Default protection is disabled with optional warn-only mode for gradual enforcement. Changes
Sequence DiagramsequenceDiagram
participant Client
participant APIComponent as API Request<br/>Component
participant SSRF as SSRF<br/>Validator
participant DNS as DNS<br/>Resolver
participant HTTP as HTTP<br/>Client
Client->>APIComponent: make_api_request(url, ...)
APIComponent->>APIComponent: Normalize URL
APIComponent->>SSRF: validate_url_for_ssrf(url)
alt SSRF Protection Enabled
SSRF->>SSRF: Check allowlist
alt Host in allowlist
SSRF-->>APIComponent: ✓ Allowed
else Direct IP
SSRF->>SSRF: is_ip_blocked(ip)
alt IP blocked
SSRF-->>APIComponent: ✗ Blocked / ⚠ Warn
else IP allowed
SSRF-->>APIComponent: ✓ Allowed
end
else Hostname
SSRF->>DNS: resolve_hostname()
alt Resolution fails
SSRF-->>APIComponent: ✗ Error / ⚠ Warn
else IPs resolved
SSRF->>SSRF: Check all IPs blocked
alt Any allowed
SSRF-->>APIComponent: ✓ Allowed
else All blocked
SSRF-->>APIComponent: ✗ Blocked / ⚠ Warn
end
end
end
else SSRF Protection Disabled
SSRF-->>APIComponent: ✓ Allowed (bypass)
end
APIComponent->>HTTP: Execute request
HTTP-->>Client: Response
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes
Pre-merge checks and finishing touches✅ Passed checks (7 passed)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 6
🧹 Nitpick comments (1)
src/lfx/src/lfx/utils/ssrf_protection.py (1)
139-162: Consider Pythonic refactor for IP blocking check.The loop checking blocked ranges (lines 155-159) can be simplified using
any()for a more Pythonic approach:try: - if isinstance(ip, str): - ip_obj = ipaddress.ip_address(ip) - else: - ip_obj = ip + ip_obj = ipaddress.ip_address(ip) if isinstance(ip, str) else ip - # Check against all blocked ranges - for blocked_range in BLOCKED_IP_RANGES: - if ip_obj in blocked_range: - return True - - return False + return any(ip_obj in blocked_range for blocked_range in BLOCKED_IP_RANGES) except (ValueError, ipaddress.AddressValueError): # If we can't parse the IP, treat it as blocked for safety return True
📜 Review details
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (5)
src/backend/tests/unit/components/data/test_api_request_component.py(3 hunks)src/lfx/src/lfx/components/data/api_request.py(4 hunks)src/lfx/src/lfx/services/settings/base.py(1 hunks)src/lfx/src/lfx/utils/ssrf_protection.py(1 hunks)src/lfx/tests/unit/utils/test_ssrf_protection.py(1 hunks)
🧰 Additional context used
📓 Path-based instructions (6)
src/backend/tests/unit/components/**/*.py
📄 CodeRabbit inference engine (.cursor/rules/backend_development.mdc)
src/backend/tests/unit/components/**/*.py: Mirror the component directory structure for unit tests in src/backend/tests/unit/components/
Use ComponentTestBaseWithClient or ComponentTestBaseWithoutClient as base classes for component unit tests
Provide file_names_mapping for backward compatibility in component tests
Create comprehensive unit tests for all new components
Files:
src/backend/tests/unit/components/data/test_api_request_component.py
{src/backend/**/*.py,tests/**/*.py,Makefile}
📄 CodeRabbit inference engine (.cursor/rules/backend_development.mdc)
{src/backend/**/*.py,tests/**/*.py,Makefile}: Run make format_backend to format Python code before linting or committing changes
Run make lint to perform linting checks on backend Python code
Files:
src/backend/tests/unit/components/data/test_api_request_component.py
src/backend/tests/unit/**/*.py
📄 CodeRabbit inference engine (.cursor/rules/backend_development.mdc)
Test component integration within flows using create_flow, build_flow, and get_build_events utilities
Files:
src/backend/tests/unit/components/data/test_api_request_component.py
src/backend/tests/**/*.py
📄 CodeRabbit inference engine (.cursor/rules/testing.mdc)
src/backend/tests/**/*.py: Unit tests for backend code must be located in the 'src/backend/tests/' directory, with component tests organized by component subdirectory under 'src/backend/tests/unit/components/'.
Test files should use the same filename as the component under test, with an appropriate test prefix or suffix (e.g., 'my_component.py' → 'test_my_component.py').
Use the 'client' fixture (an async httpx.AsyncClient) for API tests in backend Python tests, as defined in 'src/backend/tests/conftest.py'.
When writing component tests, inherit from the appropriate base class in 'src/backend/tests/base.py' (ComponentTestBase, ComponentTestBaseWithClient, or ComponentTestBaseWithoutClient) and provide the required fixtures: 'component_class', 'default_kwargs', and 'file_names_mapping'.
Each test in backend Python test files should have a clear docstring explaining its purpose, and complex setups or mocks should be well-commented.
Test both sync and async code paths in backend Python tests, using '@pytest.mark.asyncio' for async tests.
Mock external dependencies appropriately in backend Python tests to isolate unit tests from external services.
Test error handling and edge cases in backend Python tests, including using 'pytest.raises' and asserting error messages.
Validate input/output behavior and test component initialization and configuration in backend Python tests.
Use the 'no_blockbuster' pytest marker to skip the blockbuster plugin in tests when necessary.
Be aware of ContextVar propagation in async tests; test both direct event loop execution and 'asyncio.to_thread' scenarios to ensure proper context isolation.
Test error handling by mocking internal functions using monkeypatch in backend Python tests.
Test resource cleanup in backend Python tests by using fixtures that ensure proper initialization and cleanup of resources.
Test timeout and performance constraints in backend Python tests using 'asyncio.wait_for' and timing assertions.
Test Langflow's Messag...
Files:
src/backend/tests/unit/components/data/test_api_request_component.py
src/backend/**/*component*.py
📄 CodeRabbit inference engine (.cursor/rules/icons.mdc)
In your Python component class, set the
iconattribute to a string matching the frontend icon mapping exactly (case-sensitive).
Files:
src/backend/tests/unit/components/data/test_api_request_component.py
src/backend/**/components/**/*.py
📄 CodeRabbit inference engine (.cursor/rules/icons.mdc)
In your Python component class, set the
iconattribute to a string matching the frontend icon mapping exactly (case-sensitive).
Files:
src/backend/tests/unit/components/data/test_api_request_component.py
🧠 Learnings (7)
📚 Learning: 2025-07-21T14:16:14.125Z
Learnt from: CR
Repo: langflow-ai/langflow PR: 0
File: .cursor/rules/testing.mdc:0-0
Timestamp: 2025-07-21T14:16:14.125Z
Learning: Applies to src/backend/tests/**/*.py : Test Langflow's REST API endpoints in backend Python tests using the async client fixture and asserting response codes and payloads.
Applied to files:
src/backend/tests/unit/components/data/test_api_request_component.py
📚 Learning: 2025-08-05T22:51:27.961Z
Learnt from: edwinjosechittilappilly
Repo: langflow-ai/langflow PR: 0
File: :0-0
Timestamp: 2025-08-05T22:51:27.961Z
Learning: The TestComposioComponentAuth test in src/backend/tests/unit/components/bundles/composio/test_base_composio.py demonstrates proper integration testing patterns for external API components, including real API calls with mocking for OAuth completion, comprehensive resource cleanup, and proper environment variable handling with pytest.skip() fallbacks.
Applied to files:
src/backend/tests/unit/components/data/test_api_request_component.py
📚 Learning: 2025-07-18T18:25:54.486Z
Learnt from: CR
Repo: langflow-ai/langflow PR: 0
File: .cursor/rules/backend_development.mdc:0-0
Timestamp: 2025-07-18T18:25:54.486Z
Learning: Applies to src/backend/tests/unit/components/**/*.py : Create comprehensive unit tests for all new components
Applied to files:
src/backend/tests/unit/components/data/test_api_request_component.py
📚 Learning: 2025-07-21T14:16:14.125Z
Learnt from: CR
Repo: langflow-ai/langflow PR: 0
File: .cursor/rules/testing.mdc:0-0
Timestamp: 2025-07-21T14:16:14.125Z
Learning: Applies to src/backend/tests/**/*.py : Test backward compatibility across Langflow versions in backend Python tests by mapping component files to supported versions using 'VersionComponentMapping'.
Applied to files:
src/backend/tests/unit/components/data/test_api_request_component.py
📚 Learning: 2025-07-21T14:16:14.125Z
Learnt from: CR
Repo: langflow-ai/langflow PR: 0
File: .cursor/rules/testing.mdc:0-0
Timestamp: 2025-07-21T14:16:14.125Z
Learning: Applies to src/backend/tests/**/*.py : Test components that require external APIs using appropriate pytest markers (e.g., 'api_key_required', 'no_blockbuster') and environment variables for API keys.
Applied to files:
src/backend/tests/unit/components/data/test_api_request_component.py
📚 Learning: 2025-07-21T14:16:14.125Z
Learnt from: CR
Repo: langflow-ai/langflow PR: 0
File: .cursor/rules/testing.mdc:0-0
Timestamp: 2025-07-21T14:16:14.125Z
Learning: Applies to src/backend/tests/**/*.py : Test component configuration updates in backend Python tests by asserting correct updates to build configuration objects.
Applied to files:
src/backend/tests/unit/components/data/test_api_request_component.py
📚 Learning: 2025-07-21T14:16:14.125Z
Learnt from: CR
Repo: langflow-ai/langflow PR: 0
File: .cursor/rules/testing.mdc:0-0
Timestamp: 2025-07-21T14:16:14.125Z
Learning: Applies to src/backend/tests/**/*.py : Use 'anyio' and 'aiofiles' for async file operations in backend Python tests that involve file handling.
Applied to files:
src/backend/tests/unit/components/data/test_api_request_component.py
🧬 Code graph analysis (4)
src/lfx/src/lfx/utils/ssrf_protection.py (1)
src/backend/base/langflow/services/deps.py (1)
get_settings_service(122-135)
src/lfx/src/lfx/components/data/api_request.py (2)
src/lfx/src/lfx/utils/ssrf_protection.py (2)
SSRFProtectionError(35-38)validate_url_for_ssrf(204-327)src/lfx/src/lfx/custom/custom_component/component.py (1)
log(1480-1497)
src/lfx/tests/unit/utils/test_ssrf_protection.py (2)
src/lfx/src/lfx/utils/ssrf_protection.py (6)
SSRFProtectionError(35-38)get_allowed_hosts(72-82)is_host_allowed(85-136)is_ip_blocked(139-162)resolve_hostname(165-201)validate_url_for_ssrf(204-327)src/backend/tests/unit/components/data/test_api_request_component.py (1)
test_ssrf_protection_disabled_by_default(376-386)
src/backend/tests/unit/components/data/test_api_request_component.py (3)
src/lfx/src/lfx/components/data/api_request.py (2)
APIRequestComponent(45-577)make_api_request(422-487)src/lfx/src/lfx/schema/data.py (1)
Data(26-288)src/lfx/src/lfx/utils/ssrf_protection.py (1)
SSRFProtectionError(35-38)
🪛 GitHub Actions: Ruff Style Check
src/backend/tests/unit/components/data/test_api_request_component.py
[error] 393-393: SIM117 Use a single with statement with multiple contexts instead of nested with statements.
🪛 GitHub Check: Ruff Style Check (3.13)
src/lfx/src/lfx/utils/ssrf_protection.py
[failure] 155-159: Ruff (SIM110)
src/lfx/src/lfx/utils/ssrf_protection.py:155:9: SIM110 Use return any(ip_obj in blocked_range for blocked_range in BLOCKED_IP_RANGES) instead of for loop
[failure] 149-152: Ruff (SIM108)
src/lfx/src/lfx/utils/ssrf_protection.py:149:9: SIM108 Use ternary operator ip_obj = ipaddress.ip_address(ip) if isinstance(ip, str) else ip instead of if-else-block
[failure] 73-78: Ruff (D212)
src/lfx/src/lfx/utils/ssrf_protection.py:73:5: D212 Multi-line docstring summary should start at the first line
[failure] 38-38: Ruff (PIE790)
src/lfx/src/lfx/utils/ssrf_protection.py:38:5: PIE790 Unnecessary pass statement
[failure] 27-32: Ruff (I001)
src/lfx/src/lfx/utils/ssrf_protection.py:27:1: I001 Import block is un-sorted or un-formatted
src/backend/tests/unit/components/data/test_api_request_component.py
[failure] 488-489: Ruff (SIM117)
src/backend/tests/unit/components/data/test_api_request_component.py:488:9: SIM117 Use a single with statement with multiple contexts instead of nested with statements
[failure] 473-477: Ruff (SIM117)
src/backend/tests/unit/components/data/test_api_request_component.py:473:9: SIM117 Use a single with statement with multiple contexts instead of nested with statements
[failure] 458-462: Ruff (SIM117)
src/backend/tests/unit/components/data/test_api_request_component.py:458:9: SIM117 Use a single with statement with multiple contexts instead of nested with statements
[failure] 427-428: Ruff (SIM117)
src/backend/tests/unit/components/data/test_api_request_component.py:427:9: SIM117 Use a single with statement with multiple contexts instead of nested with statements
[failure] 393-394: Ruff (SIM117)
src/backend/tests/unit/components/data/test_api_request_component.py:393:9: SIM117 Use a single with statement with multiple contexts instead of nested with statements
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (13)
- GitHub Check: Run Backend Tests / Unit Tests - Python 3.10 - Group 5
- GitHub Check: Run Backend Tests / Unit Tests - Python 3.10 - Group 1
- GitHub Check: Lint Backend / Run Mypy (3.10)
- GitHub Check: Lint Backend / Run Mypy (3.11)
- GitHub Check: Run Backend Tests / Unit Tests - Python 3.10 - Group 4
- GitHub Check: Run Backend Tests / Unit Tests - Python 3.10 - Group 2
- GitHub Check: Run Backend Tests / Unit Tests - Python 3.10 - Group 3
- GitHub Check: Lint Backend / Run Mypy (3.13)
- GitHub Check: Run Backend Tests / LFX Tests - Python 3.10
- GitHub Check: Run Backend Tests / Integration Tests - Python 3.10
- GitHub Check: Test Starter Templates
- GitHub Check: Update Starter Projects
- GitHub Check: Update Component Index
🔇 Additional comments (16)
src/lfx/src/lfx/services/settings/base.py (1)
320-329: LGTM! Well-documented backward-compatible settings.The SSRF protection settings are appropriately designed with:
- Clear documentation explaining the protection scope
- Default values that maintain backward compatibility (protection disabled by default)
- Forward-looking notes about v2.0 breaking changes
- Support for flexible allowlist patterns (exact hostnames, wildcards, IPs, CIDR ranges)
The integration with the existing
CustomSourcefor comma-separated list parsing is clean.src/lfx/src/lfx/utils/ssrf_protection.py (1)
204-327: Excellent validation logic with clear error messages.The main validation function is well-structured:
- Early exit for disabled protection (line 224)
- Allowlist bypass checks before expensive DNS resolution
- Clear, actionable error messages with remediation steps
- Proper handling of both direct IPs and hostnames
- IPv6 zone ID cleanup in DNS resolution
The warn-only mode provides a smooth migration path for users.
src/lfx/src/lfx/components/data/api_request.py (4)
30-30: Good integration of SSRF protection imports.The imports are correctly placed and will provide the necessary validation capabilities.
146-157: Security-focused breaking change well-documented.Changing
follow_redirectsdefault fromTruetoFalseis the right security posture. The expanded warning message clearly explains the SSRF bypass risk and references OWASP best practices.This is a component-level change that prompts users to review, allowing them to opt-in if needed.
433-440: Effective runtime security warning.The runtime warning when redirects are enabled reinforces the security implications and will help users understand the risks during development and debugging.
454-462: SSRF validation properly integrated.The validation is correctly positioned:
- After URL normalization (line 447)
- After URL validation (line 450-452)
- Before query parameter processing (line 463-472)
The
warn_only=Trueparameter provides backward compatibility while still logging warnings to prepare users for v2.0.src/backend/tests/unit/components/data/test_api_request_component.py (4)
33-33: Test fixture correctly updated for security default.The change to
follow_redirects: Falsealigns with the component's new secure default.
346-375: Comprehensive SSRF test class added.Good test organization with a dedicated class for SSRF protection scenarios. The fixtures are properly structured and reuse the component setup pattern.
376-558: Excellent SSRF protection test coverage.The test suite comprehensively covers:
- Default disabled state
- Blocking scenarios (localhost, private networks, cloud metadata)
- Public URL allowance
- Allowlist bypass (exact hosts, CIDR ranges)
- Warn-only mode behavior
- Security warnings for redirects
- URL normalization
This provides strong confidence in the SSRF protection implementation.
411-422: Simplify nested with statements in loop.Same issue as above - combine the nested contexts.
Apply this pattern for each iteration:
- with patch.dict(os.environ, {"LANGFLOW_SSRF_PROTECTION_ENABLED": "true"}): for url in private_ips: component.url_input = url - with patch("lfx.components.data.api_request.validate_url_for_ssrf") as mock_validate: + with patch.dict(os.environ, {"LANGFLOW_SSRF_PROTECTION_ENABLED": "true"}): + for url in private_ips: + component.url_input = url + + with patch("lfx.components.data.api_request.validate_url_for_ssrf") as mock_validate: from lfx.utils.ssrf_protection import SSRFProtectionError mock_validate.side_effect = SSRFProtectionError(f"Access to {url} blocked") with pytest.raises(ValueError, match="SSRF Protection"): await component.make_api_request()Actually, the current structure is fine since the patch needs to be recreated for each iteration. The outer
withshould stay as is.Likely an incorrect or invalid review comment.
src/lfx/tests/unit/utils/test_ssrf_protection.py (6)
1-72: Well-structured configuration tests.The
TestSSRFProtectionConfigurationclass thoroughly tests:
- Default disabled state (with TODO noting v2.0 change)
- Environment variable parsing with parametrized true/false values
- Allowlist parsing for various formats
Good use of
@pytest.mark.parametrizefor comprehensive coverage.
74-143: Comprehensive IP blocking test coverage.The
TestIPBlockingclass validates all blocked IP ranges:
- Loopback addresses
- RFC 1918 private networks
- Cloud metadata endpoints (169.254.169.254)
- Special-use ranges
- IPv6 private ranges
Also tests public IP allowance and invalid IP safety handling.
195-217: Good hostname resolution tests.Tests cover:
- Public hostname resolution (using stable
dns.google)- Localhost resolution
- Invalid hostname error handling
The use of a stable public service is smart for reliable CI runs.
219-359: Extensive URL validation tests.The
TestURLValidationclass provides comprehensive coverage:
- Protection disabled mode
- Scheme validation (http/https only, blocks ftp/file)
- Direct IP blocking (loopback, private, metadata)
- Public IP/hostname allowance
- Allowlist bypass (hostname, IP, CIDR)
- Warn-only mode
- Malformed URLs
- IPv6 addresses
Excellent use of mocking for DNS resolution to test deterministically.
361-408: Realistic integration scenario tests.The
TestIntegrationScenariosclass validates real-world attack vectors:
- AWS metadata endpoint (169.254.169.254)
- Internal admin panels
- Legitimate external APIs
- Docker internal networking
These tests demonstrate the practical security value of the protection.
145-193: Remove the false bug claim and approve the tests.The
TestHostnameAllowlistclass provides thorough coverage of hostname allowlist functionality including exact matches, wildcards, IP addresses, and CIDR ranges. The implementation ofget_allowed_hosts()is correct—it expects a string from settings, splits it into a list, and returnslist[str]. Theis_host_allowed()function correctly consumes this list. No type mismatch bug exists. The test failure you may encounter is likely due to environment configuration (module import path), not a code defect.
Codecov Report❌ Patch coverage is
❌ Your project status has failed because the head coverage (39.58%) is below the target coverage (60.00%). You can increase the head coverage or adjust the target coverage. Additional details and impacted files@@ Coverage Diff @@
## main #10544 +/- ##
==========================================
+ Coverage 31.45% 31.55% +0.10%
==========================================
Files 1328 1329 +1
Lines 60153 60292 +139
Branches 8994 9022 +28
==========================================
+ Hits 18920 19025 +105
- Misses 40326 40356 +30
- Partials 907 911 +4
Flags with carried forward coverage won't be shown. Click here to find out more.
🚀 New features to boost your workflow:
|
4788e24 to
d6785e8
Compare
Adds configuration to enable SSRF protection in the API Request Component. We will leave the defaults for (most*) parameters as-is to avoid breaking changes, but have added appropriate warnings and notices to update to the more secure behaviors in the next major release.
Allows configures an allowlist for specific domains.
*The only parameter updated is in API Request Component to
follow_redirects. As this is a component-level change, we can safely update this parameter without breaking existing users' flows, as they will be prompted to manually update the component and can decline.Addresses #10355
Summary by CodeRabbit
Release Notes
New Features
Security Improvements