Skip to content

Conversation

@jordanrfrazier
Copy link
Collaborator

@jordanrfrazier jordanrfrazier commented Aug 22, 2025

Adds encryption at rest for sensitive fields in auth settings, and decrypts on retrieval.

Ignore everything but the first few files; the rest are just import re-orderings.

To test manually:

  • Run langflow with LANGFLOW_FEATURE_MCP_COMPOSER=true
  • Open project
  • Open MCP Server
  • Click Add Auth
  • Add random fields
  • run ~/Documents/langflow/langflow mcp-composer-oauth ❯ sqlite3 src/backend/base/langflow/langflow.db "SELECT name, auth_settings FROM folder WHERE name='TestProject1';" # update to your project name
TestProject1|{"auth_type": "oauth", "api_key": null, "username": null, "password": null, "bearer_token": null, "iam_endpoint": null, "oauth_host": "localhost", "oauth_port": "8080", "oauth_server_url": "http://localhost:8080", "oauth_callback_path": "http://localhost:8080/auth/callback", "oauth_client_id": "test-client-id-12345", "oauth_client_secret": "gAAAAABop8TqG0EN5qpBQpz3Qxrfg73cHFbcymdkkAvnB0xRXLj-Kr8kSiLlJJCl4bovLPs9KZdH69GbWdFNC6j4TKLdwMXYVQ==", "oauth_auth_url": "https://oauth.example.com/authorize", "oauth_token_url": "https://oauth.example.com/token", "oauth_mcp_scope": "read write execute", "oauth_provider_scope": "user:email profile openid"}

You should see an encrypted value where your secret key lives (instead of plaintext)

Summary by CodeRabbit

  • New Features

    • MCP project authentication settings are now stored encrypted, with seamless decryption when used or viewed.
    • Existing settings are automatically migrated to the new encrypted format.
    • Improved MCP config installation with explicit --sse-url and support for OAuth-related environment variables.
  • Tests

    • Added unit and API tests for encryption/decryption and project update flows.
  • Chores

    • Database migration to encrypt existing MCP auth settings.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Aug 22, 2025

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.

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

Walkthrough

Implements encryption for MCP auth_settings: adds an Alembic migration to encrypt/decrypt existing folder.auth_settings, introduces a new encryption utility module, updates API endpoints to encrypt on write and decrypt on read (plus adjusted mcp-proxy args/env), and adds unit tests for both the API behavior and encryption utilities.

Changes

Cohort / File(s) Summary
Alembic migration
src/backend/base/langflow/alembic/versions/0882f9657f22_encrypt_existing_mcp_auth_settings_.py
New migration that encrypts existing non-null folder.auth_settings on upgrade and decrypts on downgrade; handles missing dependencies gracefully; per-record error logging; JSON normalization.
API: MCP projects
src/backend/base/langflow/api/v1/mcp_projects.py
Reads: decrypts stored auth_settings before use. Writes: encrypts auth_settings before persisting. Updates install_mcp_config to pass --sse-url, set --auth_type when available, and populate OAuth-related env vars for subprocess after decryption.
Auth encryption utilities
src/backend/base/langflow/services/auth/mcp_encryption.py
New module adding SENSITIVE_FIELDS, encrypt_auth_settings, decrypt_auth_settings, and is_encrypted, leveraging existing auth utils and settings service; logs operations; idempotent behavior for already-encrypted values.
Tests: API behavior
src/backend/tests/unit/api/v1/test_mcp_projects.py
Adds test verifying auth_settings are stored encrypted (e.g., oauth_client_secret) and correctly decrypted on retrieval.
Tests: encryption utilities
src/backend/tests/unit/services/auth/test_mcp_encryption.py
New test suite covering encryption/decryption, idempotence, partial inputs, None/empty handling, backward compatibility with plaintext, and is_encrypted detection using mocked settings service.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant Client
  participant API as MCP Projects API
  participant Enc as mcp_encryption
  participant DB

  rect rgba(230,240,255,0.5)
  note right of Client: Update project MCP settings
  Client->>API: PATCH /projects/{id}/mcp (auth_settings)
  API->>Enc: encrypt_auth_settings(auth_settings)
  Enc-->>API: encrypted_auth_settings
  API->>DB: UPDATE project.auth_settings = encrypted_auth_settings
  DB-->>API: OK
  API-->>Client: 200
  end

  rect rgba(230,255,230,0.5)
  note right of Client: List project tools / Get project
  Client->>API: GET /projects/{id}
  API->>DB: SELECT project.auth_settings
  DB-->>API: encrypted_auth_settings
  API->>Enc: decrypt_auth_settings(encrypted_auth_settings)
  Enc-->>API: plaintext_auth_settings
  API-->>Client: 200 (auth_settings decrypted)
  end
Loading
sequenceDiagram
  autonumber
  participant API as MCP Projects API
  participant DB
  participant Enc as mcp_encryption
  participant Proc as mcp-proxy (subprocess)

  API->>DB: SELECT project.auth_settings
  DB-->>API: encrypted_auth_settings
  alt has auth_settings
    API->>Enc: decrypt_auth_settings(...)
    Enc-->>API: auth_settings (includes auth_type, OAuth fields)
    API->>Proc: spawn mcp-proxy<br/>args: ["mcp-proxy","--sse-url",sse_url,"--auth_type",auth_type]<br/>env: OAUTH_* derived from settings
  else no auth_settings
    API->>Proc: spawn mcp-proxy<br/>args: ["mcp-proxy","--sse-url",sse_url]
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • lucaseduoli
✨ Finishing Touches
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch mcp-composer-oauth

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@github-actions github-actions bot added enhancement New feature or request and removed enhancement New feature or request labels Aug 22, 2025
@github-actions github-actions bot added enhancement New feature or request and removed enhancement New feature or request labels Aug 22, 2025
@github-actions github-actions bot added enhancement New feature or request and removed enhancement New feature or request labels Aug 22, 2025
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (1)
src/backend/tests/unit/services/auth/test_mcp_encryption.py (1)

152-167: is_encrypted coverage is helpful; signature tweak recommended

Given the test passes None, updating the function type hints (see prior comment) will align code and tests.

🧹 Nitpick comments (6)
src/backend/tests/unit/api/v1/test_mcp_projects.py (1)

447-513: Solid end-to-end coverage for at-rest encryption; add a couple of quick wins

  • Nice: validates encryption at write and decryption at read against the real DB.
  • Nit: URL usage is mixed across this module (some calls start with "/", others not). Consider standardizing for consistency.
  • Optional: Strengthen the DB assertion by also verifying the ciphertext looks like something we produced (e.g., using is_encrypted) in addition to the length check.

You could add this assertion (imports at top as needed):

@@
-        stored_secret = updated_project.auth_settings.get("oauth_client_secret")
+        stored_secret = updated_project.auth_settings.get("oauth_client_secret")
         assert stored_secret is not None
         assert stored_secret != "super-secret-password-123"  # Should be encrypted
-        # The encrypted value should be a base64-like string (Fernet token)
+        # The encrypted value should be a base64-like string (Fernet token)
         assert len(stored_secret) > 50  # Encrypted values are longer
+        # And it should be decryptable with our key (i.e., recognized as "encrypted")
+        from langflow.services.auth.mcp_encryption import is_encrypted
+        assert is_encrypted(stored_secret)
src/backend/base/langflow/api/v1/mcp_projects.py (1)

123-131: Decryption on read works; consider not returning secrets by default

Decrypting before building the AuthSettings model is correct. However, returning plaintext secrets in a GET response can be risky. Consider an opt-in flag (e.g., include_secrets=true for owners) or redaction by default for highly sensitive fields.

src/backend/base/langflow/alembic/versions/0882f9657f22_encrypt_existing_mcp_auth_settings_.py (2)

8-16: Remove unused imports to satisfy linters

sqlmodel, Inspector, and migration are imported but unused; get_settings_service is imported in upgrade/downgrade but unused as well. Clean these up to keep make lint green.

@@
-import sqlmodel
-from sqlalchemy.engine.reflection import Inspector
-from langflow.utils import migration
+ # (removed unused imports)

And inside upgrade/downgrade:

-        from langflow.services.deps import get_settings_service
+        # get_settings_service is not needed here; encrypt/decrypt functions handle it internally

25-73: Migration logic is reasonable; watch JSON handling across backends

Writing json.dumps(...) into a JSON/JSONB column via text() works on SQLite but may double-encode on PostgreSQL unless casted. Consider using a Table-bound UPDATE with typed parameters, or CAST on PG.

Outside this hunk, an example approach:

folder = sa.Table("folder", sa.MetaData(), autoload_with=conn)
stmt = folder.update().where(folder.c.id == folder_id).values(auth_settings=encrypted_settings)
conn.execute(stmt)

This lets SQLAlchemy handle JSON serialization for the target dialect.

src/backend/base/langflow/services/auth/mcp_encryption.py (1)

88-108: Broaden type and short-circuit in is_encrypted

Tests call is_encrypted(None) and the function already guards, but the type hints say str only. Make it Optional[str | bytes] and keep the early return.

-def is_encrypted(value: str) -> bool:
+from typing import Optional, Union
+
+def is_encrypted(value: Optional[Union[str, bytes]]) -> bool:
@@
-    if not value:
+    if not value:
         return False
@@
-        auth_utils.decrypt_api_key(value, settings_service)
+        # decrypt_api_key expects str; normalize
+        to_check = value.decode() if isinstance(value, (bytes, bytearray)) else value
+        auth_utils.decrypt_api_key(to_check, settings_service)
         return True
src/backend/tests/unit/services/auth/test_mcp_encryption.py (1)

82-93: Minor: avoid unnecessary patches

These tests don’t exercise settings access (they return early for None), so patching get_settings_service isn’t needed.

-    @patch("langflow.services.auth.mcp_encryption.get_settings_service")
-    def test_encrypt_none_returns_none(self, mock_get_settings):
+    def test_encrypt_none_returns_none(self):
@@
-    @patch("langflow.services.auth.mcp_encryption.get_settings_service")
-    def test_decrypt_none_returns_none(self, mock_get_settings):
+    def test_decrypt_none_returns_none(self):
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 8af1909 and 8e89ef4.

⛔ Files ignored due to path filters (1)
  • uv.lock is excluded by !**/*.lock
📒 Files selected for processing (5)
  • src/backend/base/langflow/alembic/versions/0882f9657f22_encrypt_existing_mcp_auth_settings_.py (1 hunks)
  • src/backend/base/langflow/api/v1/mcp_projects.py (4 hunks)
  • src/backend/base/langflow/services/auth/mcp_encryption.py (1 hunks)
  • src/backend/tests/unit/api/v1/test_mcp_projects.py (1 hunks)
  • src/backend/tests/unit/services/auth/test_mcp_encryption.py (1 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
{src/backend/**/*.py,tests/**/*.py,Makefile}

📄 CodeRabbit inference engine (.cursor/rules/backend_development.mdc)

{src/backend/**/*.py,tests/**/*.py,Makefile}: Run make format_backend to format Python code before linting or committing changes
Run make lint to perform linting checks on backend Python code

Files:

  • src/backend/tests/unit/services/auth/test_mcp_encryption.py
  • src/backend/base/langflow/services/auth/mcp_encryption.py
  • src/backend/tests/unit/api/v1/test_mcp_projects.py
  • src/backend/base/langflow/alembic/versions/0882f9657f22_encrypt_existing_mcp_auth_settings_.py
  • src/backend/base/langflow/api/v1/mcp_projects.py
src/backend/tests/unit/**/*.py

📄 CodeRabbit inference engine (.cursor/rules/backend_development.mdc)

Test component integration within flows using create_flow, build_flow, and get_build_events utilities

Files:

  • src/backend/tests/unit/services/auth/test_mcp_encryption.py
  • src/backend/tests/unit/api/v1/test_mcp_projects.py
src/backend/tests/**/*.py

📄 CodeRabbit inference engine (.cursor/rules/testing.mdc)

src/backend/tests/**/*.py: Unit tests for backend code must be located in the 'src/backend/tests/' directory, with component tests organized by component subdirectory under 'src/backend/tests/unit/components/'.
Test files should use the same filename as the component under test, with an appropriate test prefix or suffix (e.g., 'my_component.py' → 'test_my_component.py').
Use the 'client' fixture (an async httpx.AsyncClient) for API tests in backend Python tests, as defined in 'src/backend/tests/conftest.py'.
When writing component tests, inherit from the appropriate base class in 'src/backend/tests/base.py' (ComponentTestBase, ComponentTestBaseWithClient, or ComponentTestBaseWithoutClient) and provide the required fixtures: 'component_class', 'default_kwargs', and 'file_names_mapping'.
Each test in backend Python test files should have a clear docstring explaining its purpose, and complex setups or mocks should be well-commented.
Test both sync and async code paths in backend Python tests, using '@pytest.mark.asyncio' for async tests.
Mock external dependencies appropriately in backend Python tests to isolate unit tests from external services.
Test error handling and edge cases in backend Python tests, including using 'pytest.raises' and asserting error messages.
Validate input/output behavior and test component initialization and configuration in backend Python tests.
Use the 'no_blockbuster' pytest marker to skip the blockbuster plugin in tests when necessary.
Be aware of ContextVar propagation in async tests; test both direct event loop execution and 'asyncio.to_thread' scenarios to ensure proper context isolation.
Test error handling by mocking internal functions using monkeypatch in backend Python tests.
Test resource cleanup in backend Python tests by using fixtures that ensure proper initialization and cleanup of resources.
Test timeout and performance constraints in backend Python tests using 'asyncio.wait_for' and timing assertions.
Test Langflow's Messag...

Files:

  • src/backend/tests/unit/services/auth/test_mcp_encryption.py
  • src/backend/tests/unit/api/v1/test_mcp_projects.py
🧬 Code graph analysis (5)
src/backend/tests/unit/services/auth/test_mcp_encryption.py (2)
src/backend/base/langflow/services/auth/mcp_encryption.py (3)
  • decrypt_auth_settings (58-85)
  • encrypt_auth_settings (22-55)
  • is_encrypted (88-107)
src/backend/base/langflow/services/auth/utils.py (1)
  • encrypt_api_key (456-460)
src/backend/base/langflow/services/auth/mcp_encryption.py (2)
src/backend/base/langflow/services/deps.py (1)
  • get_settings_service (117-130)
src/backend/base/langflow/services/auth/utils.py (2)
  • decrypt_api_key (463-489)
  • encrypt_api_key (456-460)
src/backend/tests/unit/api/v1/test_mcp_projects.py (3)
src/backend/tests/conftest.py (1)
  • logged_in_headers (497-503)
src/backend/base/langflow/services/deps.py (1)
  • session_scope (157-179)
src/backend/base/langflow/services/database/models/folder/model.py (1)
  • Folder (21-36)
src/backend/base/langflow/alembic/versions/0882f9657f22_encrypt_existing_mcp_auth_settings_.py (2)
src/backend/base/langflow/services/auth/mcp_encryption.py (2)
  • encrypt_auth_settings (22-55)
  • decrypt_auth_settings (58-85)
src/backend/base/langflow/services/deps.py (1)
  • get_settings_service (117-130)
src/backend/base/langflow/api/v1/mcp_projects.py (2)
src/backend/base/langflow/services/auth/mcp_encryption.py (2)
  • decrypt_auth_settings (58-85)
  • encrypt_auth_settings (22-55)
src/backend/base/langflow/api/v1/schemas.py (1)
  • AuthSettings (444-458)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Optimize new Python code in this PR
  • GitHub Check: Update Starter Projects
  • GitHub Check: Run Ruff Check and Format
  • GitHub Check: Ruff Style Check (3.13)
🔇 Additional comments (7)
src/backend/base/langflow/api/v1/mcp_projects.py (1)

40-41: Importing encryption helpers where they’re used is appropriate

The new imports are scoped correctly to this module and keep responsibilities clear.

src/backend/base/langflow/alembic/versions/0882f9657f22_encrypt_existing_mcp_auth_settings_.py (1)

75-123: Rollback path mirrors upgrade; ensure key stability between envs

Decrypting on downgrade is symmetric—good. Operationally, ensure the same SECRET_KEY material is present during both upgrade and runtime; otherwise, records may become undecryptable.

Provide upgrade/runbook notes to ops that SECRET_KEY must be stable and present when running Alembic migrations for this revision.

src/backend/base/langflow/services/auth/mcp_encryption.py (3)

10-19: SENSITIVE_FIELDS set looks good and future-proof

Covers typical token/secret fields. Keeping it centralized simplifies future additions.


22-56: Encryption path is correct and idempotent

  • Attempts decrypt-first to avoid double-encryption—good.
  • Copies input before mutation, avoids side effects.
  • Logs at debug without leaking values.

58-86: Decryption path handles mixed states

Gracefully tolerates plaintext legacy values—good for backward compatibility.

src/backend/tests/unit/services/auth/test_mcp_encryption.py (2)

14-28: Mock settings service is realistic and lightweight

Using a generated Fernet key via SecretStr-like mock ensures encrypt/decrypt paths are exercised faithfully.


48-67: Happy path encryption assertions hit the right things

Checks only sensitive fields are transformed.

@jordanrfrazier jordanrfrazier added this pull request to the merge queue Aug 24, 2025
@github-merge-queue github-merge-queue bot removed this pull request from the merge queue due to failed status checks Aug 24, 2025
@github-actions github-actions bot added enhancement New feature or request and removed enhancement New feature or request labels Aug 25, 2025
@jordanrfrazier jordanrfrazier added this pull request to the merge queue Aug 25, 2025
@github-actions github-actions bot added enhancement New feature or request and removed enhancement New feature or request labels Aug 25, 2025
@jordanrfrazier jordanrfrazier removed this pull request from the merge queue due to a manual request Aug 25, 2025
@github-actions github-actions bot added enhancement New feature or request and removed enhancement New feature or request labels Aug 25, 2025
@github-actions github-actions bot added enhancement New feature or request and removed enhancement New feature or request labels Aug 25, 2025
@jordanrfrazier jordanrfrazier added this pull request to the merge queue Aug 25, 2025
@github-actions github-actions bot added enhancement New feature or request and removed enhancement New feature or request labels Aug 25, 2025
@sonarqubecloud
Copy link

Merged via the queue into main with commit a642691 Aug 25, 2025
20 checks passed
@jordanrfrazier jordanrfrazier deleted the mcp-composer-oauth branch August 25, 2025 21:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants