Skip to content

fix(security): harden MCP stdio configuration#14036

Open
erichare wants to merge 6 commits into
release-1.10.3from
fix/le-1783-mcp-stdio-source-policy
Open

fix(security): harden MCP stdio configuration#14036
erichare wants to merge 6 commits into
release-1.10.3from
fix/le-1783-mcp-stdio-source-policy

Conversation

@erichare

@erichare erichare commented Jul 13, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • block tenant-controlled package sources and package-manager environment overrides for MCP stdio launchers
  • reject Docker host mounts, host/container namespaces, device access, and isolation-downgrading options
  • enforce one shared policy for API configs, embedded flow/tweak configs, the deprecated packed-string component, and the immediate pre-spawn boundary
  • recursively validate shell-wrapper payloads with an explicit depth bound
  • require structured configs to keep one executable in command and options in args, while preserving executable paths with directory spaces and the legacy packed default

Tickets

  • LE-1783 — package-source policy
  • LE-1781 — Docker host-access policy
  • LE-1777 — embedded/runtime validation
  • LE-1779 — executable-only command field

Testing

  • 80 focused LFX MCP tests passed
  • 228 backend handoff tests passed, 8 skipped
  • Ruff passed
  • pinned Biome passed
  • pre-commit passed (Biome hooks skipped because the pinned Biome check ran directly)
  • browser E2E fixtures were migrated and linted; live Playwright was not run

Summary by CodeRabbit

  • New Features

    • Added centralized security validation for MCP stdio server configurations.
    • MCP commands and arguments are handled as separate fields, with improved support for paths containing spaces.
    • Added protections against unsafe shell commands, package-source redirection, Docker host access, and sensitive environment variables.
    • Preserved trusted package sources, provider credentials, and safe isolated Docker configurations.
  • Bug Fixes

    • Unsafe configurations are now rejected before connections, sessions, storage, or database records are created.
    • Legacy MCP command formats are normalized and validated consistently.

@coderabbitai

coderabbitai Bot commented Jul 13, 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: 1bfb9ef4-b5a3-4a9d-928b-4ef7298ec287

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

MCP stdio security validation is centralized in shared policy modules and applied across API schemas, runtime connections, tool updates, and legacy components. Tests now cover command boundaries, shell wrappers, environment variables, package sources, Docker arguments, structured configurations, uploads, and frontend persistence.

Changes

MCP stdio security

Layer / File(s) Summary
Shared MCP policy modules
src/lfx/src/lfx/base/mcp/security.py, src/lfx/src/lfx/base/mcp/source_policy.py
Adds shared validation for executables, arguments, environments, shell wrappers, package sources, and Docker host-access options.
Configuration and runtime integration
src/backend/base/langflow/api/v2/schemas.py, src/lfx/src/lfx/base/mcp/util.py, src/lfx/src/lfx/components/deactivated/mcp_stdio.py
Routes API models, stdio launches, tool updates, and legacy commands through the shared validator.
Runtime and source-policy validation tests
src/lfx/tests/unit/mcp/*
Tests rejection before connection or spawn, safe configuration forwarding, HTTP behavior, executable paths, wrapper depth, and legacy normalization.
Backend security and upload tests
src/backend/tests/unit/api/v2/test_mcp_servers_file.py, src/backend/tests/unit/base/mcp/test_mcp_util.py, src/backend/tests/unit/test_mcp_command_injection_security.py
Expands coverage for upload rejection, embedded command arguments, environment variables, package sources, Docker options, and launch behavior.
Structured MCP configuration coverage
src/backend/tests/unit/components/models_and_agents/*, src/frontend/tests/extended/features/mcp-server.spec.ts
Updates component fixtures and UI tests to represent commands and arguments as separate fields across fallback, refresh, persistence, and editing flows.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant MCPServerConfig
  participant update_tools
  participant MCPStdioClient
  participant Session
  MCPServerConfig->>update_tools: command, args, env
  update_tools->>MCPStdioClient: validated stdio configuration
  MCPStdioClient->>Session: revalidate before spawn
Loading

Possibly related PRs

Suggested reviewers: jkavia, andifilhohub, ogabrielluiz, cristhianzl, edwinjosechittilappilly

🚥 Pre-merge checks | ✅ 8 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (8 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: security hardening for MCP stdio configuration.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Test Coverage For New Implementations ✅ Passed Substantive unit/regression tests were added or updated for the new MCP security, runtime validation, API, and frontend config-shape changes.
Test Quality And Coverage ✅ Passed Broad pytest/Playwright coverage hits reject/allow paths, async boundaries, legacy stdio, and upload side effects with meaningful assertions.
Test File Naming And Structure ✅ Passed PASS: Test files follow repo conventions—pytest unit modules, a Playwright feature spec, descriptive names, and both positive/negative cases are covered.
Excessive Mock Usage Warning ✅ Passed Mocks are limited to external boundaries (clients, DB, storage); several new tests are pure assertions, and no added test shows broad mock layering or core-logic mocking.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/le-1783-mcp-stdio-source-policy

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 github-actions Bot added the bug Something isn't working label Jul 13, 2026
@github-actions github-actions Bot added bug Something isn't working and removed bug Something isn't working labels Jul 13, 2026
@github-actions github-actions Bot added bug Something isn't working and removed bug Something isn't working labels Jul 13, 2026
@erichare erichare changed the title fix(security): restrict MCP stdio package sources fix(security): harden MCP stdio configuration Jul 13, 2026
@github-actions github-actions Bot added bug Something isn't working and removed bug Something isn't working labels Jul 13, 2026
@erichare erichare marked this pull request as ready for review July 13, 2026 19:47
@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 requested a review from Adam-Aghili July 13, 2026 19:49
@github-actions github-actions Bot added bug Something isn't working and removed bug Something isn't working labels Jul 13, 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: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/backend/tests/unit/components/models_and_agents/test_mcp_component.py (1)

731-761: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Test doesn't actually assert the API-provided config was used.

The trailing comment "Connect should be called with API-provided config as fallback" (Line 760) has no corresponding assertion — mock_connect is never checked. Since this test was touched to represent the new {command, args} contract, it should verify mock_connect was called with the structured api_provided_config (e.g., asserting the composed command/args), not just that get_server was queried.

🧪 Suggested assertion
             # Database should be queried first
             mock_get_server.assert_called_once()

             # Connect should be called with API-provided config as fallback
+            mock_connect.assert_called_once()
+            full_command = mock_connect.call_args.args[0]
+            assert full_command == "uvx mcp-server-api-new --api-mode"

Based on path instructions for **/test_*.py: "verify the tests actually cover the new or changed behavior rather than acting as placeholders."

🤖 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/models_and_agents/test_mcp_component.py`
around lines 731 - 761, Complete test_rest_api_new_server_scenario by asserting
mock_connect was called with the structured api_provided_config fallback,
including its command, args, and env values. Keep the existing get_server
assertion and verify the connection call reflects the new {command, args}
contract rather than leaving the trailing comment untested.

Source: Path instructions

🤖 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/lfx/src/lfx/base/mcp/security.py`:
- Around line 33-47: Update src/lfx/src/lfx/base/mcp/security.py:33-47 and
validate_mcp_stdio_config so standard npx confirmation flags are not rejected as
dangerous keywords, while preserving blocking of actual package-source or
execution-risk keywords. Update
src/lfx/tests/unit/mcp/test_stdio_source_policy.py:9-13 to route non-shell
allowed cases through validate_mcp_stdio_config, ensuring npx -y is validated by
the same runtime gate rather than only the source-policy helper.

---

Outside diff comments:
In `@src/backend/tests/unit/components/models_and_agents/test_mcp_component.py`:
- Around line 731-761: Complete test_rest_api_new_server_scenario by asserting
mock_connect was called with the structured api_provided_config fallback,
including its command, args, and env values. Keep the existing get_server
assertion and verify the connection call reflects the new {command, args}
contract rather than leaving the trailing comment untested.
🪄 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: 3d31ba09-5bbf-4227-9cc6-f1639908c62e

📥 Commits

Reviewing files that changed from the base of the PR and between 2433e87 and 548d519.

📒 Files selected for processing (15)
  • src/backend/base/langflow/api/v2/schemas.py
  • src/backend/tests/unit/api/v2/test_mcp_servers_file.py
  • src/backend/tests/unit/base/mcp/test_mcp_util.py
  • src/backend/tests/unit/components/models_and_agents/test_mcp_component.py
  • src/backend/tests/unit/components/models_and_agents/test_mcp_component_cache.py
  • src/backend/tests/unit/components/models_and_agents/test_mcp_component_dynamic.py
  • src/backend/tests/unit/components/models_and_agents/test_mcp_component_flow_reload.py
  • src/backend/tests/unit/test_mcp_command_injection_security.py
  • src/frontend/tests/extended/features/mcp-server.spec.ts
  • src/lfx/src/lfx/base/mcp/security.py
  • src/lfx/src/lfx/base/mcp/source_policy.py
  • src/lfx/src/lfx/base/mcp/util.py
  • src/lfx/src/lfx/components/deactivated/mcp_stdio.py
  • src/lfx/tests/unit/mcp/test_mcp_runtime_config_validation.py
  • src/lfx/tests/unit/mcp/test_stdio_source_policy.py

Comment on lines +33 to +47
DANGEROUS_KEYWORDS = frozenset(
{
"-c",
"-e",
"-y",
"--yes",
"pip",
"install",
"npm",
"yarn",
"pnpm",
"eval",
"exec",
}
)

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.

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

npx -y is blocked by policy, and the source-policy test masks it. Root cause: -y/--yes live in DANGEROUS_KEYWORDS, so validate_mcp_stdio_config rejects the standard non-interactive npx -y @scope/server`` launch at every runtime gate; the source-policy-only test helper never exercises that gate, so its allowed-list entry gives false confidence.

  • src/lfx/src/lfx/base/mcp/security.py#L33-L47: confirm blocking -y/--yes is intended; if not, exempt npx confirmation flags (they don't select a package source), otherwise legitimate npx -y configs break.
  • src/lfx/tests/unit/mcp/test_stdio_source_policy.py#L9-L13: route non-shell commands through validate_mcp_stdio_config so the allowed cases mirror the real runtime gate (this currently fails for npx -y, exposing the tension).
#!/bin/bash
# Confirm npx -y is rejected by the full config gate while source policy allows it.
python - <<'PY'
from lfx.base.mcp.security import validate_mcp_stdio_config
from lfx.base.mcp.source_policy import validate_mcp_stdio_source_policy
try:
    validate_mcp_stdio_source_policy("npx", ["-y", "`@modelcontextprotocol/server-filesystem`", "/workspace"])
    print("source_policy: ALLOWED")
except Exception as e:
    print("source_policy: REJECTED ->", e)
try:
    validate_mcp_stdio_config("npx", ["-y", "`@modelcontextprotocol/server-filesystem`", "/workspace"], None)
    print("full_config: ALLOWED")
except Exception as e:
    print("full_config: REJECTED ->", e)
PY
📍 Affects 2 files
  • src/lfx/src/lfx/base/mcp/security.py#L33-L47 (this comment)
  • src/lfx/tests/unit/mcp/test_stdio_source_policy.py#L9-L13
🤖 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/base/mcp/security.py` around lines 33 - 47, Update
src/lfx/src/lfx/base/mcp/security.py:33-47 and validate_mcp_stdio_config so
standard npx confirmation flags are not rejected as dangerous keywords, while
preserving blocking of actual package-source or execution-risk keywords. Update
src/lfx/tests/unit/mcp/test_stdio_source_policy.py:9-13 to route non-shell
allowed cases through validate_mcp_stdio_config, ensuring npx -y is validated by
the same runtime gate rather than only the source-policy helper.

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

github-actions Bot commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

Frontend Unit Test Coverage Report

Coverage Summary

Lines Statements Branches Functions
Coverage: 43%
43.54% (58072/133362) 69.2% (7884/11392) 41.56% (1298/3123)

Unit Test Results

Tests Skipped Failures Errors Time
4978 0 💤 0 ❌ 0 🔥 12m 15s ⏱️

@github-actions github-actions Bot added bug Something isn't working and removed bug Something isn't working labels Jul 13, 2026
@codecov

codecov Bot commented Jul 13, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 75.49669% with 74 lines in your changes missing coverage. Please review.
⚠️ Please upload report for BASE (release-1.10.3@61d6490). Learn more about missing BASE report.

Files with missing lines Patch % Lines
src/lfx/src/lfx/base/mcp/source_policy.py 71.77% 37 Missing and 22 partials ⚠️
src/lfx/src/lfx/base/mcp/security.py 83.54% 6 Missing and 7 partials ⚠️
src/lfx/src/lfx/base/mcp/util.py 80.00% 2 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@                Coverage Diff                @@
##             release-1.10.3   #14036   +/-   ##
=================================================
  Coverage                  ?   58.58%           
=================================================
  Files                     ?     2309           
  Lines                     ?   220446           
  Branches                  ?    31229           
=================================================
  Hits                      ?   129140           
  Misses                    ?    89771           
  Partials                  ?     1535           
Flag Coverage Δ
backend 65.82% <100.00%> (?)
frontend 57.64% <ø> (?)
lfx 54.93% <75.16%> (?)

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

Files with missing lines Coverage Δ
src/backend/base/langflow/api/v2/schemas.py 100.00% <100.00%> (ø)
src/lfx/src/lfx/base/mcp/util.py 15.47% <80.00%> (ø)
src/lfx/src/lfx/base/mcp/security.py 83.54% <83.54%> (ø)
src/lfx/src/lfx/base/mcp/source_policy.py 71.77% <71.77%> (ø)
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

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

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant