fix(security): harden multi-tenant surfaces - #13530
Conversation
|
Important Review skippedAuto incremental reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
WalkthroughThis PR implements multi-tenant security hardening across the Langflow/LFX codebase. It introduces new utility modules for safe env-var access, local file-path confinement, and SSRF URL validation; wires these into connectors, vector stores, model discovery, SQL, Git, and file components; adds MCP stdio command allowlist enforcement; gates agentic endpoints behind a feature flag; escapes SQL LIKE patterns; prevents credential type-confusion; and documents all new settings. ChangesMulti-tenant Security Hardening
Estimated code review effort🎯 5 (Critical) | ⏱️ ~120 minutes Possibly related PRs
Suggested labels
Suggested reviewers
🚥 Pre-merge checks | ✅ 4 | ❌ 5❌ Failed checks (1 warning, 4 inconclusive)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
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 |
26c9707 to
637764e
Compare
This comment has been minimized.
This comment has been minimized.
092ff30 to
55814d1
Compare
|
✅ Migration Validation Passed All migrations follow the Expand-Contract pattern correctly. |
✅ Test Coverage AdvisorNo source changes detected without accompanying tests. Thanks for keeping coverage up! 🎉
|
3c462e3 to
7c39928
Compare
This comment has been minimized.
This comment has been minimized.
1 similar comment
This comment has been minimized.
This comment has been minimized.
There was a problem hiding this comment.
Actionable comments posted: 17
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
src/lfx/src/lfx/components/models_and_agents/mcp_component.py (1)
514-522: 🔒 Security & Privacy | 🟠 Major | ⚡ Quick winInclude user identity in the MCP cache key before caching user-bound tools.
Line 521 only binds
current_user_idon cache miss. Whenuse_cache=True, the earlier shared cache hit returns cachedtools/tool_cachewithout callingupdate_tools, so an agentic MCP server entry can reuse tools spawned for another user. Includeself.user_idin the cache key for user-bound configs, or bypass shared caching forlangflow.agentic.mcp.Suggested direction
- cache_data = { + cache_data = { "headers": hdrs, "timeout": normalized_timeout, + "user_id": str(getattr(self, "user_id", "") or ""), }Or apply this only when the resolved MCP config targets the internal agentic MCP module.
🤖 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/models_and_agents/mcp_component.py` around lines 514 - 522, The shared MCP cache lookup in the `MCPComponent` flow can return user-bound `tools` and `tool_cache` without re-running `update_tools`, so `current_user_id` is only applied on cache miss. Update the caching logic around the `use_cache` path to include `self.user_id` in the cache key for user-specific MCP configs, or disable shared caching for the internal `langflow.agentic.mcp` target; use the existing `update_tools` and cache-handling code in `MCPComponent` to keep cached tool sets isolated per user.src/backend/tests/unit/components/git/test_gitextractor_ssrf.py (1)
13-54: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick winUse the required backend component test base and fixtures in this file.
This path matches
src/backend/tests/unit/components/**/*.py, but the new tests are written as ad hoc pytest functions instead of usingComponentTestBaseWithClient/ComponentTestBaseWithoutClientplus the requiredcomponent_class,default_kwargs, andfile_names_mappingfixtures. As per coding guidelines, "Component tests must use eitherComponentTestBaseWithClientfor components needing API access orComponentTestBaseWithoutClientfor pure logic components, and must includecomponent_class,default_kwargs, andfile_names_mappingfixtures."🤖 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/git/test_gitextractor_ssrf.py` around lines 13 - 54, The tests in this git component suite are written as standalone pytest functions, but backend component tests must use the shared base classes and fixtures. Refactor the test module to use either ComponentTestBaseWithClient or ComponentTestBaseWithoutClient as appropriate, and add the required component_class, default_kwargs, and file_names_mapping fixtures for the GitExtractorComponent and GitLoaderComponent test coverage. Keep the SSRF assertions, but move them into the component test structure so they align with the existing backend component testing pattern.Source: Coding guidelines
🤖 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.
Inline comments:
In `@src/backend/base/langflow/api/v1/projects.py`:
- Around line 509-518: The parent folder validation in the project update flow
is checking ownership against current_user.id, which can incorrectly allow
cross-owner reparenting for shared project writes. Update the parent lookup in
the project update handler to use project_owner_id (derived from
existing_project.user_id) instead of the actor, so the check in the project
update path only permits folders owned by the project owner; keep the existing
parent_id assignment on existing_project unchanged.
In `@src/backend/base/langflow/services/variable/service.py`:
- Around line 365-380: Move the db_variable.updated_at assignment in
variable/service.py so it happens only after the validation in the update flow
passes. In the relevant update path around the result of the resulting_type
check and the ValueError guard, keep the rejection logic first and only set
updated_at on db_variable after those checks succeed, so a failed
credential-to-generic transition in the service method does not leave a
timestamped dirty state.
In `@src/backend/tests/unit/agentic/mcp/test_server_user_binding.py`:
- Around line 33-34: The regression test only checks run_assistant, but
caller-supplied user_id was also removed from the other MCP flow tools. Expand
the pytest coverage in test_server_user_binding.py to assert that
inspect.signature(...) for the create/graph/component tool entrypoints also
omits user_id, using the same pattern as
test_run_assistant_does_not_accept_caller_supplied_user_id. Keep the assertions
focused on the public tool functions involved in the MCP flow so the IDOR guard
is covered across all changed paths.
In `@src/backend/tests/unit/api/v1/test_projects.py`:
- Around line 140-152: The regression test in
test_update_project_rejects_unowned_parent_id only covers a nonexistent
parent_id, so it does not exercise the cross-tenant ownership case. Update the
test to create a real folder/project owned by a different user, then use that
object’s id as parent_id in the PATCH against the original project so Project
update logic is verified against an existing чужой parent, not just a random
UUID.
In `@src/backend/tests/unit/components/git/test_gitextractor_ssrf.py`:
- Around line 47-53: The GitLoader SSRF test only asserts that build_gitloader()
raises, so it can still pass even if the dangerous URL reaches
git.Repo.clone_from first. Strengthen test_gitloader_blocks_dangerous_clone_url
by mocking or spying on the clone primitive used inside
GitLoaderComponent.build_gitloader() and asserting it is never called when
clone_url is ext::sh -c "id", while still expecting SSRFProtectionError or
ValueError.
In `@src/backend/tests/unit/test_mcp_command_injection_security.py`:
- Around line 128-145: Extend the packed-command rejection coverage in
test_command_packed_payload_with_empty_args_rejected so it also exercises the
same malicious command strings with args=None, since MCPServerConfig accepts
that frontend shape elsewhere in the file. Reuse the existing packed list and
assert ValidationError for both empty-args and None-args cases to ensure the
tokenization-based validation in MCPServerConfig.command handling cannot regress
for either input form.
In `@src/bundles/lfx-bundles/src/lfx_bundles/chroma/local_db.py`:
- Around line 219-222: The `get_vector_store_directory` call is creating the
collection directory before `enforce_local_file_access` validates it, which
allows an unsafe path to be materialized first. Update the `persist_directory`
setup in `local_db.py` so the target path is validated by
`enforce_local_file_access` before any directory creation happens, and only then
call the logic that may create the storage directory. Use the existing
`get_vector_store_directory` and `enforce_local_file_access` helpers in
`LocalDB` to preserve the restriction while preventing attacker-chosen paths
from being created.
In `@src/bundles/lfx-bundles/src/lfx_bundles/elastic/elasticsearch.py`:
- Around line 114-116: The SSRF validation in Elasticsearch connector setup only
guards elasticsearch_url, but cloud_id is also tenant-controlled and can embed
an internal host. Update the Elasticsearch client initialization logic in the
elasticsearch connector to decode the host from cloud_id and pass it through
validate_connector_url_for_ssrf as well, alongside the existing
elasticsearch_url check, so both entry points are filtered before connection.
In `@src/lfx/src/lfx/base/mcp/security.py`:
- Around line 321-347: Normalize shell exec flag handling in the security check
so wrapper detection in the logic around extract_base_command, SHELL_EXEC_FLAGS,
and SHELL_WRAPPERS is case-insensitive. The current exact-match check lets
variants like bash -lc and cmd /C slip past the wrapper guard; update the flag
parsing so wrapper-specific exec flags are recognized regardless of case before
deciding whether the following argument may be treated as the wrapped command.
In `@src/lfx/src/lfx/base/models/unified_models/credentials.py`:
- Around line 523-526: The SSRF check is only applied to the Ollama path, but
the same tenant-controlled URL handling in the credential validator still makes
outbound calls for OpenAI and Watsonx without a guard. Update the URL validation
flow in the relevant methods in credentials.py (around the existing
`validate_connector_url_for_ssrf` usage and the client-call blocks for
`OPENAI_BASE_URL` and `WATSONX_URL`) so each provider-controlled endpoint is
validated before any `requests.get` or equivalent network request. Keep the fix
localized by reusing `validate_connector_url_for_ssrf` consistently for every
outbound validation URL.
- Around line 69-72: The provider metadata fallback in the credentials
resolution path still uses a raw environment lookup, bypassing the
protected-name filter. Update the fallback logic in the unified credentials
model so the canonical provider default lookup also goes through safe_getenv,
keeping the behavior consistent with the explicit var_name path and preventing
reserved secrets from being read through provider metadata defaults.
In `@src/lfx/src/lfx/components/files_and_knowledge/save_file.py`:
- Around line 616-623: The save path logic in save_file.py should anchor
tenant-provided relative names under storage before applying
enforce_local_file_access, because Path("report") is currently being resolved
from the process working directory and can be rejected in restricted mode.
Update the file path construction in the save flow around file_name,
_adjust_file_path_with_format, and enforce_local_file_access so the final path
is built first, relative names are prefixed with config_dir only when
LANGFLOW_RESTRICT_LOCAL_FILE_ACCESS is enabled, and then the security check is
applied before mkdir or any write.
In `@src/lfx/src/lfx/components/langchain_utilities/openapi.py`:
- Around line 81-84: The YAML detection in openapi parsing is using Path.suffix
incorrectly because it compares against extensions without the leading dot, so
openapi.yaml and openapi.yml fall through to JsonSpec.from_file. Update the
suffix check in the openapi utility around the path handling logic to match the
actual values returned by Path.suffix, and keep the change scoped to the
existing branch that decides between YAML and JSON parsing.
In `@src/lfx/src/lfx/utils/file_path_security.py`:
- Around line 88-129: enforce_local_file_access currently validates the resolved
candidate path but still returns the original path object in restricted mode, so
callers can open a symlink target that was not actually checked. Update
enforce_local_file_access in file_path_security.py to return the resolved
candidate after all restriction and reserved-path checks pass, while keeping the
unrestricted branch unchanged and preserving the existing LocalFileAccessError
behavior.
In `@src/lfx/tests/unit/components/test_provider_base_url_ssrf.py`:
- Around line 77-104: These tests only assert ValueError and can still pass
after a real network attempt, so they do not verify the no-I/O SSRF contract.
Update the test cases around test_get_ollama_models_blocks_metadata,
test_lmstudio_get_model_blocks_metadata, and
test_lmstudio_embeddings_get_model_blocks_metadata to patch the outbound HTTP
helper as a sentinel and assert it is never called while the metadata URL is
blocked. Keep the existing ssrf_enabled() and exception assertions, but add a
zero-call assertion on the mocked request path to prove the request was
short-circuited before any I/O.
In `@src/lfx/tests/unit/utils/test_flow_validation.py`:
- Around line 530-543: The alias test setup in _code_interpreter_raw_graph
currently hardcodes node.display_name to "Python Interpreter", so the alias
cases only validate data["type"] and never cover display_name-based matching.
Update the helper or the affected alias tests in test_flow_validation.py so the
"Python Code Structured", "Python Function", "Smart Transform", and agent alias
cases each use their own display_name values and assert the corresponding
display_name path in the flow validation logic, ensuring both type and
display_name matching are exercised.
In `@src/lfx/tests/unit/utils/test_ssrf_protection.py`:
- Around line 599-606: This test is exercising the wrong validator: it belongs
to TestGitRepositoryURLValidation but currently calls
validate_database_url_for_ssrf instead of validate_git_repository_url, so it
does not cover the Git allowlist path. Update test_allowlist_bypass to use the
Git URL validator with a git repository URL while keeping the same ssrf settings
and mocked resolve_hostname behavior, so the allowlist bypass in
validate_git_repository_url is actually verified.
---
Outside diff comments:
In `@src/backend/tests/unit/components/git/test_gitextractor_ssrf.py`:
- Around line 13-54: The tests in this git component suite are written as
standalone pytest functions, but backend component tests must use the shared
base classes and fixtures. Refactor the test module to use either
ComponentTestBaseWithClient or ComponentTestBaseWithoutClient as appropriate,
and add the required component_class, default_kwargs, and file_names_mapping
fixtures for the GitExtractorComponent and GitLoaderComponent test coverage.
Keep the SSRF assertions, but move them into the component test structure so
they align with the existing backend component testing pattern.
In `@src/lfx/src/lfx/components/models_and_agents/mcp_component.py`:
- Around line 514-522: The shared MCP cache lookup in the `MCPComponent` flow
can return user-bound `tools` and `tool_cache` without re-running
`update_tools`, so `current_user_id` is only applied on cache miss. Update the
caching logic around the `use_cache` path to include `self.user_id` in the cache
key for user-specific MCP configs, or disable shared caching for the internal
`langflow.agentic.mcp` target; use the existing `update_tools` and
cache-handling code in `MCPComponent` to keep cached tool sets isolated per
user.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 8200362b-a07f-4f3e-9a7a-f80f9d724c74
📒 Files selected for processing (101)
docs/docs/Develop/api-keys-and-authentication.mdxsrc/backend/base/langflow/agentic/api/deps.pysrc/backend/base/langflow/agentic/api/router.pysrc/backend/base/langflow/agentic/helpers/validation.pysrc/backend/base/langflow/agentic/mcp/server.pysrc/backend/base/langflow/agentic/services/user_components_overlay.pysrc/backend/base/langflow/api/router.pysrc/backend/base/langflow/api/v1/authz_roles.pysrc/backend/base/langflow/api/v1/authz_teams.pysrc/backend/base/langflow/api/v1/files.pysrc/backend/base/langflow/api/v1/monitor.pysrc/backend/base/langflow/api/v1/projects.pysrc/backend/base/langflow/api/v1/users.pysrc/backend/base/langflow/api/v2/mcp.pysrc/backend/base/langflow/api/v2/schemas.pysrc/backend/base/langflow/helpers/flow.pysrc/backend/base/langflow/initial_setup/starter_projects/Content Aggregator.jsonsrc/backend/base/langflow/initial_setup/starter_projects/Deep Research Agent.jsonsrc/backend/base/langflow/initial_setup/starter_projects/Financial Report Parser.jsonsrc/backend/base/langflow/initial_setup/starter_projects/Hybrid Search RAG.jsonsrc/backend/base/langflow/initial_setup/starter_projects/Instagram Copywriter.jsonsrc/backend/base/langflow/initial_setup/starter_projects/Market Research.jsonsrc/backend/base/langflow/initial_setup/starter_projects/Portfolio Website Code Generator.jsonsrc/backend/base/langflow/initial_setup/starter_projects/Price Deal Finder.jsonsrc/backend/base/langflow/initial_setup/starter_projects/SaaS Pricing.jsonsrc/backend/base/langflow/initial_setup/starter_projects/Sequential Tasks Agents.jsonsrc/backend/base/langflow/initial_setup/starter_projects/Simple Agent.jsonsrc/backend/base/langflow/initial_setup/starter_projects/Social Media Agent.jsonsrc/backend/base/langflow/initial_setup/starter_projects/Structured Data Analysis Agent.jsonsrc/backend/base/langflow/initial_setup/starter_projects/Text Sentiment Analysis.jsonsrc/backend/base/langflow/initial_setup/starter_projects/Travel Planning Agents.jsonsrc/backend/base/langflow/interface/initialize/loading.pysrc/backend/base/langflow/memory.pysrc/backend/base/langflow/services/adapters/deployment/watsonx_orchestrate/core/execution.pysrc/backend/base/langflow/services/database/models/message/model.pysrc/backend/base/langflow/services/tracing/repository.pysrc/backend/base/langflow/services/variable/service.pysrc/backend/tests/unit/agentic/mcp/test_server_user_binding.pysrc/backend/tests/unit/agentic/test_assistant_codeexec_gates.pysrc/backend/tests/unit/api/v1/test_files.pysrc/backend/tests/unit/api/v1/test_projects.pysrc/backend/tests/unit/components/data_source/test_web_search.pysrc/backend/tests/unit/components/files_and_knowledge/test_directory_component.pysrc/backend/tests/unit/components/git/test_gitextractor_ssrf.pysrc/backend/tests/unit/components/languagemodels/test_deepseek.pysrc/backend/tests/unit/components/languagemodels/test_xai.pysrc/backend/tests/unit/components/llm_operations/test_lambda_filter.pysrc/backend/tests/unit/components/test_connector_ssrf.pysrc/backend/tests/unit/services/variable/test_service.pysrc/backend/tests/unit/test_mcp_command_injection_security.pysrc/bundles/datastax/src/lfx_datastax/components/datastax/astradb_cql.pysrc/bundles/lfx-bundles/src/lfx_bundles/chroma/chroma.pysrc/bundles/lfx-bundles/src/lfx_bundles/chroma/local_db.pysrc/bundles/lfx-bundles/src/lfx_bundles/clickhouse/clickhouse.pysrc/bundles/lfx-bundles/src/lfx_bundles/elastic/elasticsearch.pysrc/bundles/lfx-bundles/src/lfx_bundles/faiss/faiss.pysrc/bundles/lfx-bundles/src/lfx_bundles/git/git.pysrc/bundles/lfx-bundles/src/lfx_bundles/git/gitextractor.pysrc/bundles/lfx-bundles/src/lfx_bundles/milvus/milvus.pysrc/bundles/lfx-bundles/src/lfx_bundles/qdrant/qdrant.pysrc/bundles/lfx-bundles/src/lfx_bundles/upstash/upstash.pysrc/lfx/src/lfx/_assets/component_index.jsonsrc/lfx/src/lfx/base/data/base_file.pysrc/lfx/src/lfx/base/knowledge_bases/backends/base.pysrc/lfx/src/lfx/base/knowledge_bases/ingestion_sources/connector_base.pysrc/lfx/src/lfx/base/mcp/security.pysrc/lfx/src/lfx/base/mcp/util.pysrc/lfx/src/lfx/base/models/groq_model_discovery.pysrc/lfx/src/lfx/base/models/model_utils.pysrc/lfx/src/lfx/base/models/unified_models/credentials.pysrc/lfx/src/lfx/components/data_source/csv_to_data.pysrc/lfx/src/lfx/components/data_source/json_to_data.pysrc/lfx/src/lfx/components/data_source/sql_executor.pysrc/lfx/src/lfx/components/data_source/web_search.pysrc/lfx/src/lfx/components/deactivated/mcp_stdio.pysrc/lfx/src/lfx/components/files_and_knowledge/directory.pysrc/lfx/src/lfx/components/files_and_knowledge/file.pysrc/lfx/src/lfx/components/files_and_knowledge/save_file.pysrc/lfx/src/lfx/components/langchain_utilities/csv_agent.pysrc/lfx/src/lfx/components/langchain_utilities/json_agent.pysrc/lfx/src/lfx/components/langchain_utilities/openapi.pysrc/lfx/src/lfx/components/langchain_utilities/sql.pysrc/lfx/src/lfx/components/langchain_utilities/sql_database.pysrc/lfx/src/lfx/components/llm_operations/lambda_filter.pysrc/lfx/src/lfx/components/models_and_agents/mcp_component.pysrc/lfx/src/lfx/interface/initialize/loading.pysrc/lfx/src/lfx/services/settings/groups/security.pysrc/lfx/src/lfx/services/variable/service.pysrc/lfx/src/lfx/utils/env_var_security.pysrc/lfx/src/lfx/utils/file_path_security.pysrc/lfx/src/lfx/utils/flow_validation.pysrc/lfx/src/lfx/utils/ssrf_protection.pysrc/lfx/src/lfx/utils/util_strings.pysrc/lfx/tests/unit/components/test_provider_base_url_ssrf.pysrc/lfx/tests/unit/interface/test_loading_no_env_fallback.pysrc/lfx/tests/unit/mcp/test_mcp_stdio_security.pysrc/lfx/tests/unit/utils/test_env_var_security.pysrc/lfx/tests/unit/utils/test_file_path_security.pysrc/lfx/tests/unit/utils/test_flow_validation.pysrc/lfx/tests/unit/utils/test_ssrf_protection.pysrc/lfx/tests/unit/utils/test_util_strings.py
Three critical issues for multi-tenant deployments where users may not author custom components: 1. Code-execution core components (Python Interpreter/REPL, Python Code Structured tool, Smart Transform) are official, so their class-code hash is valid and they pass the allow_custom_components=False policy, yet they execute arbitrary user Python from their input fields. Add the LANGFLOW_BLOCK_CODE_INTERPRETER_COMPONENTS setting (default off), enforced in flow_validation at the Graph.from_payload choke point with recursion into nested flows so all build paths are covered. 2. LambdaFilterComponent evaluated an LLM-generated lambda with full builtins (prompt-injection -> RCE). Reject escape gadgets via validate_code_safety and eval with safe_builtins() (reuses python_repl_security). 3. The global-variable -> env-var fallback did os.getenv(<tenant-supplied name>), letting any tenant read LANGFLOW_SECRET_KEY / DATABASE_URL etc. Add env_var_security.safe_getenv with a reserved-name denylist and apply it at all fallback sites in lfx and langflow.
…holes Four more critical issues for multi-tenant deployments where tenants use all core components but cannot author custom ones: 1. Variable Credential->Generic type-confusion: PATCH /variables flipping a credential row's type to Generic without a value left the Fernet ciphertext in place; get_all then decrypted it and returned plaintext via GET /variables, exposing the server's shared provider keys. Reject the transition (write path) and never decrypt a Fernet-token value labeled Generic (read path). 2. SQL Database components (sql_executor, langchain sql/sql_database) accepted arbitrary connection URIs -> SSRF to internal DBs and sqlite:////abs/path local file read/write. Add validate_database_url_for_ssrf: host validated against SSRF blocked ranges (default-on), local-file dialects blocked under LANGFLOW_RESTRICT_LOCAL_FILE_ACCESS so single-tenant sqlite keeps working. 3. Web Search RSS/web fetches used bare requests.get with no SSRF guard (cloud-metadata cred theft). Route the RSS URL and result-link fetches through validate_url_for_ssrf. 4. File/Directory/JSON-to-Data/CSV-to-Data read arbitrary server files via uncontained resolve_path. Add LANGFLOW_RESTRICT_LOCAL_FILE_ACCESS (default off) + enforce_local_file_access, confining resolved paths to the storage data dir at every read sink. resolve_path itself is unchanged so persistence-dir components are unaffected.
Five more critical issues for multi-tenant deployments where tenants use all core components + curated trusted custom components but cannot author custom ones. All distinct from the issues fixed in 6e4ec5a and 1cdc305. 1. MCP stdio flow-embedded command execution (RCE): a tenant-built flow can embed an MCP stdio config (command/args/env) directly in the MCPTools component value, which reached `bash -c "exec <command>"` with no validation -- the MCPServerConfig allowlist only ran at the REST /api/v2/mcp/servers layer, never in the flow-execution path, and mcp_servers_locked / allow_custom_components=False do not cover it. New single-source lfx.base.mcp.security (constants + validate_mcp_stdio_config) enforced at the update_tools sink and the legacy deactivated/mcp_stdio sink; MCPServerConfig now imports the shared constants/helper so the two enforcement points cannot drift. MCP HTTP/SSE url is now SSRF-validated too. 2. Code-agent components bypass the code-interpreter lockdown: CodeActAgentSmolagents (smolagents LocalPythonExecutor) and OpenDsStarAgent (bare exec) run LLM-generated Python in-process but were absent from CODE_EXECUTION_COMPONENT_TYPES, so LANGFLOW_BLOCK_CODE_INTERPRETER_COMPONENTS=true did not block them. Added both class names + display-name aliases. 3. Git components clone arbitrary tenant URLs: GitExtractor/GitLoader pass repository_url /clone_url to git clone -> ext:: remote-helper RCE, file://+local-path file read, leading-'-' option injection, internal-host SSRF. Add validate_git_repository_url (always blocks remote helpers + option injection; blocks local-file clones under SSRF or local-file restriction; SSRF-validates network hosts incl. scp-like). 4. Home Assistant reflective SSRF -> cloud-metadata credential theft: List States (GET) and Control (POST) fetch f"{base_url}/api/.." and reflect the body to the tenant; a trailing '#' reaches the IMDS credential path. Route base_url through validate_url_for_ssrf before the request. 5. Model-provider base_url SSRF in build-config discovery: Ollama/LM Studio fetch a tenant-set base_url (even on field edit, no flow run) with no SSRF guard. Validate before each fetch. (Consistent with the existing url.py hard-block posture: local model servers need LANGFLOW_SSRF_ALLOWED_HOSTS or SSRF disabled.) Tests: 220 lfx + 121 backend tests pass; ruff clean.
…commits Follow-up to 6e4ec5a / 1cdc305 / c31eeb8 from an extensive PR review. Fixes real defects in those commits and adds opt-in SSRF for connector components. No backwards-incompatible default behavior: all host-blocking SSRF is opt-in, and local/private hosts keep working out of the box. Defect fixes (always active; only affect abuse/abnormal inputs, not legit flows): - MCP stdio: close a command-injection bypass where a tenant packs the whole payload into `command` with empty `args` (e.g. "bash -c '<payload>'"). The validator now tokenizes the command, and MCPServerConfig delegates to the shared validate_mcp_stdio_config (single source of truth). - Code-exec block list: add the missing "Python Code Structured" display-name alias and the PythonFunction component so a hash-valid node can't bypass the block. - Env-var fallback: route the remaining tenant-controlled os.getenv sites through safe_getenv (VariableService, credentials, knowledge-base sources), harden the GetEnvVar component, and expand the infra-secret denylist. - web_search + Home Assistant: allow_redirects=False so a 3xx can't bypass the SSRF guard. - git: enforce the scheme allowlist regardless of the SSRF toggle; confine the Local repo_path under LANGFLOW_RESTRICT_LOCAL_FILE_ACCESS. - api_request: reduce the Content-Disposition filename to a basename (no path traversal). Opt-in connector SSRF (new LANGFLOW_CONNECTOR_SSRF_VALIDATION_ENABLED, default off): - New validate_connector_url_for_ssrf / validate_connector_database_url_for_ssrf wrappers that no-op unless the flag is set, so connectors keep reaching localhost/private hosts by default. When enabled they defer to LANGFLOW_SSRF_PROTECTION_ENABLED / _ALLOWED_HOSTS. - Applied to vector stores (chroma/clickhouse/qdrant/elasticsearch/opensearch/milvus/ supabase/upstash/weaviate), the SQL Database components, glean, astradb_cql, model discovery (litellm/huggingface/xai/deepseek/groq/watsonx), Ollama (chat + embeddings), LM Studio, Home Assistant, and the MCP HTTP-mode URL. - The SQL local-file dialect restriction stays on its own LANGFLOW_RESTRICT_LOCAL_FILE_ACCESS toggle, independent of the connector flag. Local file-access confinement (no-op unless LANGFLOW_RESTRICT_LOCAL_FILE_ACCESS=true): CSV/JSON/OpenAPI agents + save_file write. Docs: document LANGFLOW_CONNECTOR_SSRF_VALIDATION_ENABLED + a multi-tenant recommendation in api-keys-and-authentication.mdx. Re-export the MCP constants from langflow.api.v2.schemas for backwards compatibility.
- Log (instead of silently swallowing) settings-read failures in the fail-open enable-switch helpers so a security control silently disabling is observable: restrict_local_file_access and connector_ssrf_validation_enabled gates. - Log when get_all() skips a GENERIC variable holding ciphertext (likely a CREDENTIAL row relabeled GENERIC) instead of dropping it silently. - Remove unreachable non-http/https scheme branch in SSRF validators (_validate_url_scheme already raises) and fix the misleading comment claiming such schemes are "not subject to SSRF protection". - Close the unbalanced paren in the WebSearch SSRF-blocked message. - Correct getenvvar comment: only LANGFLOW_*/LFX_* are prefix-matched; AWS protection is specific names, not an AWS_* glob. - Fix three code-execution denylist comments to point at the real exec sites (get_function, LocalPythonInterpreter, external agents.ds_star executor). - Correct LANGFLOW_SSRF_PROTECTION_ENABLED docs default (True, not False). - Add symlink-escape containment tests for enforce_local_file_access (the docstring promises symlink resolution; previously untested).
…oggles Address PR-review findings on the connector SSRF wrapper and docs: - validate_connector_url_for_ssrf: raise a clear, actionable error for a scheme-less / host-less connector URL (e.g. Milvus "host:19530") instead of the shared validator's confusing "Invalid URL scheme ''". The message tells the operator to use an explicit http(s) scheme and notes that allowlisting alone does not permit a scheme-less host (the format gate runs before the allowlist check). Only fires when host validation would actually run (global SSRF on); stays a no-op otherwise. - Document the DNS-rebinding residual in the wrapper docstring: connectors hand the URL to third-party clients that re-resolve DNS at connect time and expose no pinned-IP hook (would break TLS SNI), so unlike api_request this guard is validate-then-connect. Literal-IP targets (metadata, RFC1918) are blocked identically. - Docs: add a "Multi-tenant component hardening" section covering LANGFLOW_BLOCK_CODE_INTERPRETER_COMPONENTS and LANGFLOW_RESTRICT_LOCAL_FILE_ACCESS, recommended alongside LANGFLOW_ALLOW_CUSTOM_COMPONENTS=false. - Tests: add TestConnectorURLValidation (disabled no-op, metadata blocked, scheme-less clear-error, no-op when global SSRF off).
…undary restrict_local_file_access confined reads to config_dir, but config_dir IS the storage dir AND holds the server-managed secrets as siblings of the per-flow upload subdirs: secret_key (Fernet master key), private_key.pem / public_key.pem (JWT signing keys), and the SQLite DB (save_db_in_config_dir). A tenant File-component input of "<flow>/../secret_key" routed through build_full_path (no '..' check) resolved back to <config_dir>/secret_key, passed the is_relative_to(config_dir) boundary, and was read -- disclosing the key that decrypts every tenant's stored credentials. The control thus failed its own stated goal under the very multi-tenant mode it adds. enforce_local_file_access now denies the exact config_dir locations of secret_key / private_key.pem / public_key.pem and the sqlite DB derived from database_url (including the -wal / -shm / -journal sidecars that hold the same row data, and the async sqlite+aiosqlite:/// + ?query URL forms), even though they resolve inside the boundary. Matched at exact location only, not by basename, so a tenant upload that merely shares a reserved name inside a flow subdir stays readable. Fails safe on non-sqlite / empty / unavailable settings. Known limitation (tracked separately): this does not scope reads per tenant, so cross-tenant reads of <config_dir>/<other_flow_id>/<file> uploads remain possible and need per-user/per-flow scoping in a follow-up. Tests: reserved file blocked, traversal-to-reserved blocked, DB + WAL/SHM/ journal sidecars blocked, async+query URL blocked, same-named upload in a flow subdir still allowed.
…e route footguns Agentic MCP cross-tenant read/write (Issue 14): the langflow-agentic stdio MCP server's flow/component tools took user_id as a caller-supplied (often optional, defaulting to None) parameter, so an authenticated tenant could read or write ANY tenant's flow data/component values by id -- reachable by embedding `python -m langflow.agentic.mcp` in a flow's MCP stdio config, independent of the agentic_experience flag. Bind the acting user from the authenticated request instead of trusting the caller: - agentic/mcp/server.py: drop user_id from all 9 flow/component tools; add _bound_user_id() reading LANGFLOW_AGENTIC_USER_ID and failing closed when absent. - lfx.base.mcp.security: add AGENTIC_USER_ID_ENV_VAR / AGENTIC_MCP_MODULE; add langflow_agentic_user_id to DANGEROUS_ENV_VARS so a tenant stdio config cannot supply it. - lfx.base.mcp.util.update_tools: new current_user_id; after validation, inject the authenticated id into the spawn env when the command targets the agentic module (auto-provisioned server AND any tenant-authored config -> tenant only ever binds their own id). Callers pass it (MCPTools component, v2 mcp servers). Route footguns (round-4 review): - api/v1/projects.py update_project: validate the supplied parent_id references a folder owned by the caller (404 otherwise) instead of assigning it blind. - helpers/flow.py get_flow_by_id_or_endpoint_name: document the user_id=None unscoped contract and the requirement that callers pass an authenticated id. Docs: add a "Session cookie hardening" section for LANGFLOW_ACCESS_SECURE / ACCESS_HTTPONLY / ACCESS_SAME_SITE with the JS-frontend and HTTP caveats. Tests: MCP stdio denylist + update_tools inject/fail-closed; agentic server _bound_user_id env/fail-closed; update_project unowned-parent rejection.
The /files/images/{flow_id}/{file_name} endpoint streamed stored bytes with a
content type derived from the file extension and no anti-sniffing/disposition
headers. A tenant-uploaded SVG (served image/svg+xml) or HTML would execute
inline in the app origin when the URL is opened directly. The route is
owner-gated (so this is self-XSS, not cross-tenant), but harden it to match the
v1/v2 download_file endpoints: add X-Content-Type-Options: nosniff and
Content-Disposition: attachment. attachment forces a download on direct
navigation (image/svg+xml is scriptable regardless of nosniff); <img>/blob
embedding -- the intended use -- is unaffected.
Test asserts both headers in test_download_image_for_browser.
|
Build successful! ✅ |
|
Build successful! ✅ |
|
Build successful! ✅ |
1 similar comment
|
Build successful! ✅ |
|
Build successful! ✅ |
The release-1.11.0 merge brought in the mcp/execution-signals migration head alongside this branch's security/mcp-server head, leaving the migration graph with two heads and breaking every job that boots the database. Standard no-op merge revision.
|
Build successful! ✅ |
1 similar comment
|
Build successful! ✅ |
|
Build successful! ✅ |
|
Build successful! ✅ |
|
Build successful! ✅ |
Summary
Rebases the security-hardening sweep onto
release-1.11.0while preserving the individual commit series.Security fixes introduced beyond
release-1.11.0Validation
uv run ruff checkon changed Python filesuv run ruff format . --check --config pyproject.tomlSummary by CodeRabbit
Security
Documentation