Skip to content

fix(security): run trusted server code on unauthenticated public flow builds#13540

Open
erichare wants to merge 4 commits into
release-1.10.1from
fix/public-flow-enforce-hash-gate
Open

fix(security): run trusted server code on unauthenticated public flow builds#13540
erichare wants to merge 4 commits into
release-1.10.1from
fix/public-flow-enforce-hash-gate

Conversation

@erichare

@erichare erichare commented Jun 8, 2026

Copy link
Copy Markdown
Collaborator

Summary

Follow-up to the H1-3754930 public-flow code-execution hardening. The unauthenticated public build path POST /api/v1/build_public_tmp/{flow_id}/flow builds a public flow as its owner and executes its components — each node's stored code is run via eval_custom_component_code. The custom-component hash gate (validate_flow_for_current_settings) is a no-op under the default allow_custom_components=true, so a public flow containing a plain CustomComponent — or any node carrying arbitrary code — would execute on that path without authentication.

This was raised on the transitive-execution PR: the existing type-based blocks (PythonREPL/Smart Transform/RunFlow/…) don't cover arbitrary custom code, and CustomComponent can't be blocklisted by type (a relabelled node forges any type).

Why not just enforce the hash gate on the public path

Enforcing the hash gate (allow-list of code matching current templates) rejects legitimate flows whose built-in component code has merely drifted across versions — the gate can't distinguish "old but legit built-in" from "arbitrary code in a known-type node" (both just fail to match the current hash). A standard chatbot flow is flagged outdated and blocked; this would break the public playground on every upgrade.

The fix

On the public path, run the server's trusted code instead of trusting the node's stored bytes:

  • Known component type → its stored code is replaced with the server's current code for that type. Version drift can no longer break the build, and a node relabelled to a known type cannot smuggle in its own bytes (the server's code runs).
  • Unknown / custom component type carrying code → rejected (400 This flow cannot be executed.).
  • Codeless nodes (group/note containers) are left untouched; inlined sub-flows are handled recursively.

The build then runs from this server-sanitized data (passed via the existing data param; the caller never supplies it — it is derived from the stored flow). Authenticated builds are unaffected.

Opt-out

Operators who knowingly want public flows to run custom component code can restore the prior DB-loaded build (which honors allow_custom_components) with LANGFLOW_ALLOW_PUBLIC_CUSTOM_COMPONENTS=true (default false).

Changes

  • lfx/utils/flow_validation.py: collect_component_code_lookups, _substitute_trusted_node_code, _ensure_component_code_lookups, prepare_public_flow_build.
  • lfx/services/settings/base.py: allow_public_custom_components (default false).
  • api/v1/chat.py: public route substitutes/blocks via prepare_public_flow_build and builds from the sanitized data.

Test plan

  • src/lfx/tests/unit/utils/test_flow_validation.py (17 passed): trusted-code substitution, unknown-type block, relabelled-code neutralization, opt-in passthrough, recursion, fail-closed, empty/no-op.
  • src/backend/tests/unit/test_chat_endpoint.py::test_build_public_tmp_rejects_custom_component (new) — unknown custom component on the public path → 400.
  • Existing *public_tmp* route suite (21 passed) — standard flows still build (substitution avoids the outdated break); no regression.
  • ruff check / ruff format clean.

Notes

Independent of, and complementary to, the transitive RunFlow/SubFlow/FlowTool block on fix/public-flow-code-exec-rce (both target release-1.10.0; expect a trivial merge in the build_public_tmp validation block depending on merge order). Per SECURITY.md, vulnerability reports go through HackerOne — this PR is the remediation for an internally-tracked follow-up.

Summary by CodeRabbit

  • New Features

    • Added a new configuration setting to control custom component usage in public flows.
  • Bug Fixes

    • Enhanced security for public flow execution to reject unrecognized custom components and enforce trusted component templates.
  • Tests

    • Added comprehensive test coverage for public flow sanitization and custom component validation.

… builds

The unauthenticated public build path (POST /api/v1/build_public_tmp/{flow_id}/flow)
builds a public flow as its owner and executes its components, running each node's
stored `code` via eval_custom_component_code. The custom-component hash gate
(validate_flow_for_current_settings) is a no-op under the default
allow_custom_components=true, so a public flow containing a plain CustomComponent —
or any node carrying arbitrary code — would execute on that path without
authentication (follow-up to H1-3754930).

Enforcing the hash gate on the public path is not viable: it rejects legitimate
flows whose built-in component code has merely drifted across versions ("outdated"),
which would break the public playground on every upgrade.

Instead, on the public path substitute the server's trusted code for each known
component type and reject nodes whose type is not a known server component:
- known type -> stored code replaced with the server's current code, so version
  drift cannot break the build and a relabelled node cannot smuggle in its bytes;
- unknown/custom type carrying code -> rejected (400);
- codeless nodes (group/note) untouched; inlined sub-flows handled recursively.

The build then runs from this server-sanitized data. Operators who knowingly want
public flows to run custom component code can restore the prior DB-loaded build with
LANGFLOW_ALLOW_PUBLIC_CUSTOM_COMPONENTS=true (default false).

Authenticated builds are unaffected.
@coderabbitai

coderabbitai Bot commented Jun 8, 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: 2e5a9f26-a9c0-4cda-8342-cab5727f8a5b

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 adds server-side code sanitization for unauthenticated public flow builds. Public flows now have their component code replaced with server-trusted templates to prevent execution of user-provided code by anonymous users. A new settings flag controls opt-in behavior for custom components, and the endpoint rejects flows containing unknown component types.

Changes

Public Flow Sanitization and Component Validation

Layer / File(s) Summary
Public Component Sanitization Settings
src/lfx/src/lfx/services/settings/base.py
New allow_public_custom_components boolean setting (default False) controls whether unauthenticated public builds enforce the component-template allow-list independently of allow_custom_components.
Public Flow Sanitization Core Logic
src/lfx/src/lfx/utils/flow_validation.py
collect_component_code_lookups maps component types and aliases to server-provided trusted code. _substitute_trusted_node_code recursively rewrites node code for known components, blocks unknown types, and recurses into nested subflows. _ensure_component_code_lookups asynchronously loads component templates with fail-closed semantics. prepare_public_flow_build orchestrates sanitization: reads settings, optionally delegates to existing validation if public custom components are allowed, otherwise deep-copies the graph, loads and substitutes trusted code, and raises CustomComponentValidationError if any nodes remain blocked.
Sanitization Unit Tests
src/lfx/tests/unit/utils/test_flow_validation.py
Comprehensive tests verify component code lookup with type and alias mapping, trusted-code substitution without mutating caller input, blocking of unknown custom components, recursion into inlined subflows, opt-in behavior deferring to standard validation, fail-closed rejection when templates are unavailable, and graceful fallback on empty/malformed inputs. Test helpers construct fake server components, configurable settings, and node fixtures.
Build Public Endpoint Integration
src/backend/base/langflow/api/v1/chat.py, src/backend/tests/unit/test_chat_endpoint.py
Chat endpoint /build_public_tmp now imports and calls prepare_public_flow_build to sanitize flow data, passing the result to start_flow_build via FlowDataRequest instead of always loading from the database. New security test test_build_public_tmp_rejects_custom_component verifies the endpoint rejects flows with unknown custom components, returning 400 with "This flow cannot be executed." error detail.

🎯 3 (Moderate) | ⏱️ ~25 minutes

Suggested labels

bug, lgtm

Suggested reviewers

  • Cristhianzl
  • Adam-Aghili
🚥 Pre-merge checks | ✅ 8 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Test Quality And Coverage ⚠️ Warning Tests lack coverage for unhashable component_type values. Review flagged TypeError risk when component_type is non-string (dict, list), but no test validates graceful handling of this edge case. Add tests for malformed component_type values (dict, list, int, etc.) to ensure they don't bypass security error handling.
✅ 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 accurately and specifically describes the main security fix: substituting trusted server code on unauthenticated public flow builds, which is the core change across all modified files.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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 PR includes comprehensive tests: 11 new unit tests for sanitization logic, 1 integration test, parametrized edge cases, and tests for all 4 new public functions in correct project naming conventions.
Test File Naming And Structure ✅ Passed Test files follow test_*.py naming, pytest structure, descriptive names, logical organization with helper functions, edge case coverage with error tests, and both positive/negative scenario testing.
Excessive Mock Usage Warning ✅ Passed Core logic tested without mocks (4 unit tests), external dependencies properly mocked, lightweight test doubles used, integration test verifies end-to-end behavior.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/public-flow-enforce-hash-gate

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 and usage tips.

@erichare erichare requested a review from ogabrielluiz June 8, 2026 19:13
@github-actions github-actions Bot added the bug Something isn't working label Jun 8, 2026
@github-actions

github-actions Bot commented Jun 8, 2026

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.

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

🧹 Nitpick comments (1)
src/lfx/tests/unit/utils/test_flow_validation.py (1)

115-149: ⚡ Quick win

Add a regression test for malformed non-string component_type values.

Current tests don’t pin behavior when node.data.type is unhashable (e.g., dict). Add a case that asserts prepare_public_flow_build rejects it via CustomComponentValidationError (not a raw TypeError).

Suggested test shape
+@pytest.mark.asyncio
+async def test_prepare_public_flow_build_rejects_malformed_component_type(monkeypatch):
+    from lfx.utils import flow_validation as fv
+
+    monkeypatch.setattr("lfx.services.deps.get_settings_service", lambda: _public_settings())
+    monkeypatch.setattr(fv, "_ensure_component_code_lookups", AsyncMock(return_value={"ChatInput": "# trusted"}))
+
+    flow = {
+        "nodes": [
+            {
+                "id": "x",
+                "data": {
+                    "id": "x",
+                    "type": {"bad": "type"},
+                    "node": {"display_name": "BadType", "template": {"code": {"value": "print(1)"}}},
+                },
+            }
+        ],
+        "edges": [],
+    }
+    with pytest.raises(CustomComponentValidationError):
+        await fv.prepare_public_flow_build(flow)

As per coding guidelines, tests should include edge cases and error conditions for comprehensive coverage.

Also applies to: 169-244

🤖 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/tests/unit/utils/test_flow_validation.py` around lines 115 - 149, Add
a regression test that constructs a node whose component_type / node.data.type
is a non-string/unhashable value (e.g., a dict) and assert that calling
prepare_public_flow_build raises CustomComponentValidationError (not a raw
TypeError); locate this alongside existing flow_validation tests (e.g., near
test_substitute_trusted_node_code*), import prepare_public_flow_build and
CustomComponentValidationError from lfx.utils.flow_validation, pass the
malformed node into prepare_public_flow_build, and use an explicit
assertRaises/pytest.raises for CustomComponentValidationError to confirm the
correct error is raised.

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/lfx/src/lfx/utils/flow_validation.py`:
- Around line 297-301: The code checks component_type for truthiness then does
"component_type in type_to_code" which can raise TypeError for
unhashable/non-string values; update the branch that currently uses
node_data.get("type") so you first verify isinstance(component_type, str) (or
otherwise hashable) before testing membership in type_to_code (and treat
non-string types as invalid/missing so they follow the existing policy error
path), referencing the variables component_type, node_data, type_to_code and
code_field to find and fix the block.

---

Nitpick comments:
In `@src/lfx/tests/unit/utils/test_flow_validation.py`:
- Around line 115-149: Add a regression test that constructs a node whose
component_type / node.data.type is a non-string/unhashable value (e.g., a dict)
and assert that calling prepare_public_flow_build raises
CustomComponentValidationError (not a raw TypeError); locate this alongside
existing flow_validation tests (e.g., near test_substitute_trusted_node_code*),
import prepare_public_flow_build and CustomComponentValidationError from
lfx.utils.flow_validation, pass the malformed node into
prepare_public_flow_build, and use an explicit assertRaises/pytest.raises for
CustomComponentValidationError to confirm the correct error is raised.
🪄 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: 7c2b27d2-cbe4-4f06-a51f-f150285a28fe

📥 Commits

Reviewing files that changed from the base of the PR and between 4339011 and 448f173.

📒 Files selected for processing (5)
  • src/backend/base/langflow/api/v1/chat.py
  • src/backend/tests/unit/test_chat_endpoint.py
  • src/lfx/src/lfx/services/settings/base.py
  • src/lfx/src/lfx/utils/flow_validation.py
  • src/lfx/tests/unit/utils/test_flow_validation.py

Comment thread src/lfx/src/lfx/utils/flow_validation.py
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
@codecov

codecov Bot commented Jun 8, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 67.74194% with 30 lines in your changes missing coverage. Please review.
⚠️ Please upload report for BASE (release-1.10.1@d6d1692). Learn more about missing BASE report.

Files with missing lines Patch % Lines
src/lfx/src/lfx/utils/flow_validation.py 67.41% 19 Missing and 10 partials ⚠️
src/backend/base/langflow/api/v1/chat.py 50.00% 1 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@                Coverage Diff                @@
##             release-1.10.1   #13540   +/-   ##
=================================================
  Coverage                  ?   58.48%           
=================================================
  Files                     ?     2302           
  Lines                     ?   219245           
  Branches                  ?    32951           
=================================================
  Hits                      ?   128220           
  Misses                    ?    89560           
  Partials                  ?     1465           
Flag Coverage Δ
backend 65.14% <50.00%> (?)
frontend 57.82% <ø> (?)
lfx 54.34% <68.13%> (?)

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

Files with missing lines Coverage Δ
...c/lfx/src/lfx/services/settings/groups/security.py 90.00% <100.00%> (ø)
src/backend/base/langflow/api/v1/chat.py 52.61% <50.00%> (ø)
src/lfx/src/lfx/utils/flow_validation.py 74.20% <67.41%> (ø)
🚀 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.

@erichare erichare requested a review from Adam-Aghili June 8, 2026 19:23
@github-actions github-actions Bot added bug Something isn't working and removed bug Something isn't working labels Jun 8, 2026
@github-actions

github-actions Bot commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

Frontend Unit Test Coverage Report

Coverage Summary

Lines Statements Branches Functions
Coverage: 43%
43.28% (57622/133133) 69.22% (7829/11310) 41.49% (1291/3111)

Unit Test Results

Tests Skipped Failures Errors Time
4940 0 💤 0 ❌ 0 🔥 12m 11s ⏱️

@erichare erichare changed the base branch from release-1.10.0 to release-1.10.1 June 10, 2026 21:10
@github-actions github-actions Bot added bug Something isn't working and removed bug Something isn't working labels Jun 10, 2026
Combine the two complementary public-flow hardening defenses (report
H1-3754930) that landed on separate branches:

- prepare_public_flow_build (this PR): substitutes the server's trusted
  code into every known component and rejects unrecognized custom
  components on the unauthenticated public build path.
- validate_public_flow_no_code_execution (release-1.10.1): blocks
  code-execution component types (Python REPL/Interpreter, legacy Python
  Code Structured tool, Smart Transform) and flow-invoking components
  (Run Flow, Sub Flow, Flow as Tool — the transitive case).

Both now run on the public path in chat.py: the type/hash block first
(fail-fast, unconditional), then trusted-code substitution. The
allow_public_custom_components opt-in only widens custom-component trust;
it does not re-open the code-execution/flow-invocation block.

Conflict resolution notes:
- settings/base.py was refactored into mixins on release-1.10.1; moved
  the new allow_public_custom_components field into the SecuritySettings
  group and updated its docstring to describe the substitution behavior.
- flow_validation.py and test_flow_validation.py: kept both sides' added
  functions/tests (disjoint). Removed a duplicated blocked.append block
  left by an earlier web edit.
@github-actions github-actions Bot added bug Something isn't working and removed bug Something isn't working labels Jun 10, 2026
…ld list

The public-flow trusted-code fix added the allow_public_custom_components
setting to SecuritySettings, which bumped Settings.model_fields to 147 and
tripped test_field_count_unchanged (147 != 146). Add the field to the
curated EXPECTED_FIELDS set under a 1.10.1 section.
@github-actions github-actions Bot added bug Something isn't working and removed bug Something isn't working labels Jun 10, 2026
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