Skip to content

fix(security): harden multi-tenant surfaces - #13530

Merged
erichare merged 70 commits into
release-1.11.0from
security-hardening
Jul 13, 2026
Merged

fix(security): harden multi-tenant surfaces#13530
erichare merged 70 commits into
release-1.11.0from
security-hardening

Conversation

@jordanrfrazier

@jordanrfrazier jordanrfrazier commented Jun 7, 2026

Copy link
Copy Markdown
Collaborator

Summary

Rebases the security-hardening sweep onto release-1.11.0 while preserving the individual commit series.

Security fixes introduced beyond release-1.11.0

  • Hardens code-execution surfaces with authenticated-flow component blocking, expanded code-exec aliases, agentic assistant codegen gates, and safer LambdaFilter validation.
  • Adds local-file-access restriction enforcement across file/data components, SQL agents, Save File, and vector-store persistence paths, including extra denial for server secrets and database files inside the storage boundary.
  • Keeps the release branch's strict SSRF framework and extends it across connectors, provider base URLs, Web Search/RSS, SQL DB URLs, Git URLs, Chroma server hosts, Watsonx Orchestrate, Ollama, LM Studio, Home Assistant, and related tests.
  • Centralizes MCP stdio security checks, expands dangerous environment-variable denylisting, restricts Docker args, and binds agentic MCP tools to the authenticated user.
  • Prevents credential leakage through environment fallback denylisting and Variable service type-confusion/Fernet-shaped Generic value guards.
  • Tightens API/data leakage edges for image downloads, message ordering, LIKE wildcard handling, project parent ownership, and Watsonx Orchestrate deployment target selection.
  • Updates generated component/starter assets and multi-tenant tracing/session-cookie documentation.

Validation

  • uv run ruff check on changed Python files
  • uv run ruff format . --check --config pyproject.toml
  • Targeted backend regression tests for SSRF, restricted local files, and project validation
  • Broader backend security batch: 237 passed, 80 skipped
  • LFX security batch: 333 passed, 4 skipped

Summary by CodeRabbit

  • Security

    • Strengthened protections for multi-tenant deployments, including tighter access controls for file paths, environment variables, cookies, and agentic execution features.
    • Added safer handling for network connections and searches to reduce exposure to internal or blocked destinations.
    • Improved filtering and validation for searchable fields and project parent changes.
  • Documentation

    • Updated security guidance and recommended settings for authentication and multi-tenant hardening.

@coderabbitai

coderabbitai Bot commented Jun 7, 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: 9c0c4366-ad70-48af-8a30-edd50e89767f

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

Changes

Multi-tenant Security Hardening

Layer / File(s) Summary
Core security utility modules
src/lfx/src/lfx/utils/env_var_security.py, src/lfx/src/lfx/utils/file_path_security.py, src/lfx/src/lfx/utils/util_strings.py, src/lfx/src/lfx/utils/ssrf_protection.py
Introduces safe_getenv/is_protected_env_var (env-var reserved-name denylist), enforce_local_file_access/LocalFileAccessError (storage-boundary confinement), escape_like_pattern (SQL LIKE escaping), and extends SSRF protection with validate_connector_url_for_ssrf, validate_database_url_for_ssrf, validate_git_repository_url, and a connector-SSRF toggle.
SecuritySettings flags and MCP stdio validation module
src/lfx/src/lfx/services/settings/groups/security.py, src/lfx/src/lfx/base/mcp/security.py
Adds four new SecuritySettings boolean fields (connector_ssrf_validation_enabled, block_code_interpreter_components, restrict_local_file_access, mcp_server_docker_hardening). Introduces a dedicated MCP stdio security module with validate_mcp_stdio_config, MCPStdioSecurityError, and all command/env/docker policy constants.
Flow validation: code-execution component blocking
src/lfx/src/lfx/utils/flow_validation.py
Expands CODE_EXECUTION_COMPONENT_TYPES to a larger shared frozenset, adds _find_code_execution_components (recursive) and check_code_execution_components_and_raise, and wires the block_code_interpreter_components flag into validate_flow_for_current_settings.
Replace os.getenv with safe_getenv across loading/credential/variable paths
src/backend/base/langflow/interface/initialize/loading.py, src/lfx/src/lfx/interface/initialize/loading.py, src/lfx/src/lfx/base/knowledge_bases/backends/base.py, src/lfx/src/lfx/base/knowledge_bases/ingestion_sources/connector_base.py, src/lfx/src/lfx/base/models/unified_models/credentials.py, src/lfx/src/lfx/services/variable/service.py
Replaces all direct os.getenv/os.environ calls with safe_getenv in component loading, credential resolution, knowledge-base backends, and variable service env-fallback paths to block reserved/sensitive name leakage.
SSRF validation wired into connectors, models, and vector stores
src/bundles/datastax/..., src/bundles/lfx-bundles/src/lfx_bundles/chroma/*, src/bundles/lfx-bundles/src/lfx_bundles/clickhouse/..., src/bundles/lfx-bundles/src/lfx_bundles/elastic/..., src/bundles/lfx-bundles/src/lfx_bundles/faiss/..., src/bundles/lfx-bundles/src/lfx_bundles/git/*, src/bundles/lfx-bundles/src/lfx_bundles/milvus/..., src/bundles/lfx-bundles/src/lfx_bundles/qdrant/..., src/bundles/lfx-bundles/src/lfx_bundles/upstash/..., src/lfx/src/lfx/base/models/groq_model_discovery.py, src/lfx/src/lfx/base/models/model_utils.py, src/lfx/src/lfx/components/data_source/sql_executor.py, src/lfx/src/lfx/components/data_source/web_search.py, src/lfx/src/lfx/components/deactivated/mcp_stdio.py, src/lfx/src/lfx/components/langchain_utilities/sql*.py
Calls SSRF URL validators before outbound connections in all connector, vector store, model discovery, SQL, Git, and MCP stdio paths; adds allow_redirects=False to HTTP calls; reports SSRF-blocked fetch failures distinctly.
enforce_local_file_access wired into file/directory components
src/lfx/src/lfx/base/data/base_file.py, src/lfx/src/lfx/components/data_source/csv_to_data.py, src/lfx/src/lfx/components/data_source/json_to_data.py, src/lfx/src/lfx/components/files_and_knowledge/*, src/lfx/src/lfx/components/langchain_utilities/csv_agent.py, src/lfx/src/lfx/components/langchain_utilities/json_agent.py, src/lfx/src/lfx/components/langchain_utilities/openapi.py
Applies enforce_local_file_access to all tenant-controlled file path resolutions (read and write) to confine paths to the storage directory when LANGFLOW_RESTRICT_LOCAL_FILE_ACCESS is enabled.
API-layer security hardening
src/backend/base/langflow/api/v1/authz_roles.py, src/backend/base/langflow/api/v1/authz_teams.py, src/backend/base/langflow/api/v1/files.py, src/backend/base/langflow/api/v1/monitor.py, src/backend/base/langflow/api/v1/projects.py, src/backend/base/langflow/api/v1/users.py, src/backend/base/langflow/api/v2/schemas.py, src/backend/base/langflow/memory.py, src/backend/base/langflow/services/database/models/message/model.py, src/backend/base/langflow/services/tracing/repository.py, src/backend/base/langflow/services/variable/service.py, src/backend/base/langflow/services/adapters/deployment/watsonx_orchestrate/core/execution.py, src/backend/base/langflow/helpers/flow.py
Escapes SQL LIKE patterns in all search endpoints; adds order_by allowlist in monitor and memory endpoints; adds nosniff/attachment headers to image downloads; validates parent folder ownership on project reparent (IDOR fix); pins WatsonX agent_id to deployment_id; delegates MCPServerConfig validation to shared validate_mcp_stdio_config; blocks credential→generic type-flip without new value.
Agentic endpoint gating and MCP server user binding
src/backend/base/langflow/agentic/api/deps.py, src/backend/base/langflow/agentic/api/router.py, src/backend/base/langflow/api/router.py, src/backend/base/langflow/agentic/mcp/server.py, src/backend/base/langflow/agentic/helpers/validation.py, src/backend/base/langflow/agentic/services/user_components_overlay.py, src/lfx/src/lfx/base/mcp/util.py, src/lfx/src/lfx/components/models_and_agents/mcp_component.py, src/lfx/src/lfx/components/llm_operations/lambda_filter.py
Adds require_agentic_experience FastAPI dependency gate on agentic and files/sessions subrouters; introduces _bound_user_id() to derive acting user from server-injected env var; removes caller-supplied user_id from all MCP tool signatures; gates custom component execution and overlay on allow_custom_components; wires current_user_id into update_tools with stdio validation; sandboxes LLM-generated lambda eval with safe_builtins.
Starter project JSON updates
src/backend/base/langflow/initial_setup/starter_projects/*
Updates embedded UnifiedWebSearch component code and code_hash in 8 starter project JSONs to report SSRF-blocked fetches distinctly; updates SaaS Pricing SQL component with SSRF validation; minor note-node field reordering across all touched JSONs.
Tests and documentation
docs/docs/Develop/api-keys-and-authentication.mdx, src/backend/tests/unit/..., src/lfx/tests/unit/...
Adds unit tests for all new security utilities and wiring points; updates existing tests for allow_redirects=False; adds SSRF default changes, multi-tenant component hardening, and session cookie hardening documentation sections.

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related PRs

  • langflow-ai/langflow#13538: Implements the same block_code_interpreter_components security gate in lfx/utils/flow_validation.py for the unauthenticated public-build path using the same CODE_EXECUTION_COMPONENT_TYPES concept.
  • langflow-ai/langflow#13874: Modifies the same MCPServerConfig validation and DANGEROUS_ENV_VARS handling in src/backend/base/langflow/api/v2/schemas.py to centralize/block dangerous env vars via MCP stdio security enforcement.
  • langflow-ai/langflow#13900: Changes MCP stdio security and launch behavior in lfx.base.mcp.util — removes shell wrappers and adds env-var denylist enforcement in MCPStdioClient._connect_to_server, overlapping with update_tools stdio validation added here.

Suggested labels

bug

Suggested reviewers

  • erichare
  • Jkavia
  • Adam-Aghili
🚥 Pre-merge checks | ✅ 4 | ❌ 5

❌ Failed checks (1 warning, 4 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 63.04% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Test Coverage For New Implementations ❓ Inconclusive Repository clone failed, so this custom check could not run with code access. Retry the review run. If this persists, inspect pre-merge custom-check logs for infrastructure or agent runtime failures.
Test Quality And Coverage ❓ Inconclusive Repository clone failed, so this custom check could not run with code access. Retry the review run. If this persists, inspect pre-merge custom-check logs for infrastructure or agent runtime failures.
Test File Naming And Structure ❓ Inconclusive Repository clone failed, so this custom check could not run with code access. Retry the review run. If this persists, inspect pre-merge custom-check logs for infrastructure or agent runtime failures.
Excessive Mock Usage Warning ❓ Inconclusive Repository clone failed, so this custom check could not run with code access. Retry the review run. If this persists, inspect pre-merge custom-check logs for infrastructure or agent runtime failures.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is concise and accurately summarizes the PR’s main security hardening focus on multi-tenant surfaces.
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.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch security-hardening

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.

@github-actions

This comment has been minimized.

@jordanrfrazier
jordanrfrazier changed the base branch from release-1.10.0 to release-1.11.0 June 20, 2026 12:03
@erichare
erichare force-pushed the security-hardening branch from 092ff30 to 55814d1 Compare June 30, 2026 18:25
@github-actions

Copy link
Copy Markdown
Contributor

Migration Validation Passed

All migrations follow the Expand-Contract pattern correctly.

@erichare
erichare marked this pull request as ready for review June 30, 2026 18:28
@github-actions

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 force-pushed the security-hardening branch from 3c462e3 to 7c39928 Compare June 30, 2026 18:29
@erichare erichare changed the title Security hardening fix(security): harden multi-tenant surfaces Jun 30, 2026
@github-actions github-actions Bot added the bug Something isn't working label Jun 30, 2026
@github-actions

This comment has been minimized.

1 similar comment
@github-actions

This comment has been minimized.

@github-actions github-actions Bot added bug Something isn't working and removed bug Something isn't working labels Jun 30, 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.

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 win

Include user identity in the MCP cache key before caching user-bound tools.

Line 521 only binds current_user_id on cache miss. When use_cache=True, the earlier shared cache hit returns cached tools/tool_cache without calling update_tools, so an agentic MCP server entry can reuse tools spawned for another user. Include self.user_id in the cache key for user-bound configs, or bypass shared caching for langflow.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 win

Use 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 using ComponentTestBaseWithClient / ComponentTestBaseWithoutClient plus the required component_class, default_kwargs, and file_names_mapping fixtures. As per coding guidelines, "Component tests must use either ComponentTestBaseWithClient for components needing API access or ComponentTestBaseWithoutClient for pure logic components, and must include component_class, default_kwargs, and file_names_mapping fixtures."

🤖 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

📥 Commits

Reviewing files that changed from the base of the PR and between 9d4810c and 7c39928.

📒 Files selected for processing (101)
  • docs/docs/Develop/api-keys-and-authentication.mdx
  • src/backend/base/langflow/agentic/api/deps.py
  • src/backend/base/langflow/agentic/api/router.py
  • src/backend/base/langflow/agentic/helpers/validation.py
  • src/backend/base/langflow/agentic/mcp/server.py
  • src/backend/base/langflow/agentic/services/user_components_overlay.py
  • src/backend/base/langflow/api/router.py
  • src/backend/base/langflow/api/v1/authz_roles.py
  • src/backend/base/langflow/api/v1/authz_teams.py
  • src/backend/base/langflow/api/v1/files.py
  • src/backend/base/langflow/api/v1/monitor.py
  • src/backend/base/langflow/api/v1/projects.py
  • src/backend/base/langflow/api/v1/users.py
  • src/backend/base/langflow/api/v2/mcp.py
  • src/backend/base/langflow/api/v2/schemas.py
  • src/backend/base/langflow/helpers/flow.py
  • src/backend/base/langflow/initial_setup/starter_projects/Content Aggregator.json
  • src/backend/base/langflow/initial_setup/starter_projects/Deep Research Agent.json
  • src/backend/base/langflow/initial_setup/starter_projects/Financial Report Parser.json
  • src/backend/base/langflow/initial_setup/starter_projects/Hybrid Search RAG.json
  • src/backend/base/langflow/initial_setup/starter_projects/Instagram Copywriter.json
  • src/backend/base/langflow/initial_setup/starter_projects/Market Research.json
  • src/backend/base/langflow/initial_setup/starter_projects/Portfolio Website Code Generator.json
  • src/backend/base/langflow/initial_setup/starter_projects/Price Deal Finder.json
  • src/backend/base/langflow/initial_setup/starter_projects/SaaS Pricing.json
  • src/backend/base/langflow/initial_setup/starter_projects/Sequential Tasks Agents.json
  • src/backend/base/langflow/initial_setup/starter_projects/Simple Agent.json
  • src/backend/base/langflow/initial_setup/starter_projects/Social Media Agent.json
  • src/backend/base/langflow/initial_setup/starter_projects/Structured Data Analysis Agent.json
  • src/backend/base/langflow/initial_setup/starter_projects/Text Sentiment Analysis.json
  • src/backend/base/langflow/initial_setup/starter_projects/Travel Planning Agents.json
  • src/backend/base/langflow/interface/initialize/loading.py
  • src/backend/base/langflow/memory.py
  • src/backend/base/langflow/services/adapters/deployment/watsonx_orchestrate/core/execution.py
  • src/backend/base/langflow/services/database/models/message/model.py
  • src/backend/base/langflow/services/tracing/repository.py
  • src/backend/base/langflow/services/variable/service.py
  • src/backend/tests/unit/agentic/mcp/test_server_user_binding.py
  • src/backend/tests/unit/agentic/test_assistant_codeexec_gates.py
  • src/backend/tests/unit/api/v1/test_files.py
  • src/backend/tests/unit/api/v1/test_projects.py
  • src/backend/tests/unit/components/data_source/test_web_search.py
  • src/backend/tests/unit/components/files_and_knowledge/test_directory_component.py
  • src/backend/tests/unit/components/git/test_gitextractor_ssrf.py
  • src/backend/tests/unit/components/languagemodels/test_deepseek.py
  • src/backend/tests/unit/components/languagemodels/test_xai.py
  • src/backend/tests/unit/components/llm_operations/test_lambda_filter.py
  • src/backend/tests/unit/components/test_connector_ssrf.py
  • src/backend/tests/unit/services/variable/test_service.py
  • src/backend/tests/unit/test_mcp_command_injection_security.py
  • src/bundles/datastax/src/lfx_datastax/components/datastax/astradb_cql.py
  • src/bundles/lfx-bundles/src/lfx_bundles/chroma/chroma.py
  • src/bundles/lfx-bundles/src/lfx_bundles/chroma/local_db.py
  • src/bundles/lfx-bundles/src/lfx_bundles/clickhouse/clickhouse.py
  • src/bundles/lfx-bundles/src/lfx_bundles/elastic/elasticsearch.py
  • src/bundles/lfx-bundles/src/lfx_bundles/faiss/faiss.py
  • src/bundles/lfx-bundles/src/lfx_bundles/git/git.py
  • src/bundles/lfx-bundles/src/lfx_bundles/git/gitextractor.py
  • src/bundles/lfx-bundles/src/lfx_bundles/milvus/milvus.py
  • src/bundles/lfx-bundles/src/lfx_bundles/qdrant/qdrant.py
  • src/bundles/lfx-bundles/src/lfx_bundles/upstash/upstash.py
  • src/lfx/src/lfx/_assets/component_index.json
  • src/lfx/src/lfx/base/data/base_file.py
  • src/lfx/src/lfx/base/knowledge_bases/backends/base.py
  • src/lfx/src/lfx/base/knowledge_bases/ingestion_sources/connector_base.py
  • src/lfx/src/lfx/base/mcp/security.py
  • src/lfx/src/lfx/base/mcp/util.py
  • src/lfx/src/lfx/base/models/groq_model_discovery.py
  • src/lfx/src/lfx/base/models/model_utils.py
  • src/lfx/src/lfx/base/models/unified_models/credentials.py
  • src/lfx/src/lfx/components/data_source/csv_to_data.py
  • src/lfx/src/lfx/components/data_source/json_to_data.py
  • src/lfx/src/lfx/components/data_source/sql_executor.py
  • src/lfx/src/lfx/components/data_source/web_search.py
  • src/lfx/src/lfx/components/deactivated/mcp_stdio.py
  • src/lfx/src/lfx/components/files_and_knowledge/directory.py
  • src/lfx/src/lfx/components/files_and_knowledge/file.py
  • src/lfx/src/lfx/components/files_and_knowledge/save_file.py
  • src/lfx/src/lfx/components/langchain_utilities/csv_agent.py
  • src/lfx/src/lfx/components/langchain_utilities/json_agent.py
  • src/lfx/src/lfx/components/langchain_utilities/openapi.py
  • src/lfx/src/lfx/components/langchain_utilities/sql.py
  • src/lfx/src/lfx/components/langchain_utilities/sql_database.py
  • src/lfx/src/lfx/components/llm_operations/lambda_filter.py
  • src/lfx/src/lfx/components/models_and_agents/mcp_component.py
  • src/lfx/src/lfx/interface/initialize/loading.py
  • src/lfx/src/lfx/services/settings/groups/security.py
  • src/lfx/src/lfx/services/variable/service.py
  • src/lfx/src/lfx/utils/env_var_security.py
  • src/lfx/src/lfx/utils/file_path_security.py
  • src/lfx/src/lfx/utils/flow_validation.py
  • src/lfx/src/lfx/utils/ssrf_protection.py
  • src/lfx/src/lfx/utils/util_strings.py
  • src/lfx/tests/unit/components/test_provider_base_url_ssrf.py
  • src/lfx/tests/unit/interface/test_loading_no_env_fallback.py
  • src/lfx/tests/unit/mcp/test_mcp_stdio_security.py
  • src/lfx/tests/unit/utils/test_env_var_security.py
  • src/lfx/tests/unit/utils/test_file_path_security.py
  • src/lfx/tests/unit/utils/test_flow_validation.py
  • src/lfx/tests/unit/utils/test_ssrf_protection.py
  • src/lfx/tests/unit/utils/test_util_strings.py

Comment thread src/backend/base/langflow/api/v1/projects.py Outdated
Comment thread src/backend/base/langflow/services/variable/service.py Outdated
Comment thread src/backend/tests/unit/agentic/mcp/test_server_user_binding.py Outdated
Comment thread src/backend/tests/unit/api/v1/test_projects.py Outdated
Comment thread src/backend/tests/unit/components/git/test_gitextractor_ssrf.py Outdated
Comment thread src/lfx/src/lfx/components/langchain_utilities/openapi.py Outdated
Comment thread src/lfx/src/lfx/utils/file_path_security.py Outdated
Comment thread src/lfx/tests/unit/components/test_provider_base_url_ssrf.py
Comment thread src/lfx/tests/unit/utils/test_flow_validation.py Outdated
Comment thread src/lfx/tests/unit/utils/test_ssrf_protection.py Outdated
@github-actions

github-actions Bot commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

Frontend Unit Test Coverage Report

Coverage Summary

Lines Statements Branches Functions
Coverage: 45%
45.73% (63206/138199) 69.88% (8608/12318) 43.85% (1425/3249)

Unit Test Results

Tests Skipped Failures Errors Time
5270 0 💤 0 ❌ 0 🔥 18m 51s ⏱️

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.
@github-actions

github-actions Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Build successful! ✅
Deploying docs draft.
Deploy successful! View draft

@github-actions

github-actions Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Build successful! ✅
Deploying docs draft.
Deploy successful! View draft

@github-actions

github-actions Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Build successful! ✅
Deploying docs draft.
Deploy successful! View draft

1 similar comment
@github-actions

github-actions Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Build successful! ✅
Deploying docs draft.
Deploy successful! View draft

@github-actions

github-actions Bot commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

Build successful! ✅
Deploying docs draft.
Deploy successful! View draft

erichare added 2 commits July 13, 2026 08:30
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.
@github-actions

github-actions Bot commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

Build successful! ✅
Deploying docs draft.
Deploy successful! View draft

1 similar comment
@github-actions

github-actions Bot commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

Build successful! ✅
Deploying docs draft.
Deploy successful! View draft

@github-actions

github-actions Bot commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

Build successful! ✅
Deploying docs draft.
Deploy successful! View draft

@github-actions

github-actions Bot commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

Build successful! ✅
Deploying docs draft.
Deploy successful! View draft

@github-actions

github-actions Bot commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

Build successful! ✅
Deploying docs draft.
Deploy successful! View draft

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working fix-index lgtm This PR has been approved by a maintainer needs-docs

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants