18208 Commits

Author SHA1 Message Date
a277209fe4 ci: push nightly .devX Docker images to langflowai/langflow and fix db-migration-validation image references (#13836)
* ci: push nightly .devX docker images to langflowai/langflow in addition to langflowai/langflow-nightly

Mirrors PyPI behaviour where .devX releases are published under the
'langflow' package name (not 'langflow-nightly'). The Docker nightly
workflow now tags and pushes every nightly image to both repos:

- langflowai/langflow-nightly:{ver}  (existing)
- langflowai/langflow:{ver}          (new — matches PyPI)
- langflowai/langflow-nightly:latest (existing)
- langflowai/langflow:latest         (new)

Same mirroring applied to the base and main-all variants, and to GHCR.

The arch-specific intermediate tags (used to assemble multi-arch
manifests) remain under the '-nightly' repos; only the final merged
manifests are additionally published to the canonical 'langflow' repos.

Fixes: LE-1667

* ci: update db-migration-validation to use canonical langflowai/langflow image

The db-migration-validation workflow was still referencing the old
langflowai/langflow-nightly image for its nightly upgrade step.
Now that .devX images are also pushed to langflowai/langflow (canonical
repo, matching PyPI), update all consumer-side references:

- nightly_build.yml: nightly_tag now passes langflowai/langflow:<tag>
- db-migration-validation.yml: default and shell fallbacks updated to
  langflowai/langflow:latest; inline comments updated to match

The langflowai/langflow-nightly images remain in use as intermediate
arch-specific staging tags inside docker-nightly-build.yml only.

Relates to: LE-1667

* ci: fix stale langflow-nightly package name reference in comment

The nightly is published as canonical .devN pre-releases of langflow /
langflow-base (not as separate langflow-nightly / langflow-base-nightly
packages). Update the generate-shared-tag step comment to accurately
reflect this since the rename that happened in the 1.10 release cycle.

Relates to: LE-1353, LE-1667

* fix(ci): do not set :latest on canonical langflow repo for nightly .devX images

Nightly .devX images should not overwrite the stable :latest tag on
langflowai/langflow. The :latest (and :base-latest) floating tags are
only set on the -nightly repos (langflowai/langflow-nightly), which
are explicitly nightly-only. The canonical langflowai/langflow repo
only receives the versioned tag (e.g. :1.11.0.dev20250522), matching
PyPI behaviour where langflow==1.11.0.dev20250522 is published but
never becomes the 'latest' release index entry.

Addresses PR review comment from @jordanrfrazier.

Relates to: LE-1667

* fix(ci): prevent shell injection from inputs.nightly_tag in db-migration-validation

Pass inputs.nightly_tag through an env: block and reference it as
$INPUT_NIGHTLY_TAG in the shell script rather than expanding the
GitHub Actions expression directly inside run:. Direct expansion of
${{ inputs.* }} in shell scripts allows an attacker-controlled input
to inject arbitrary shell commands (CWE-78).

Flagged by CodeRabbit security review.

Relates to: LE-1667
2026-06-25 19:16:10 -07:00
4da60c4b12 fix: keep nightly prerelease wheels explicit (#13817) 2026-06-24 13:17:42 -07:00
7db38d1abb fix: unblock nightly bundle install resolution 2026-06-23 20:31:21 -07:00
1a60220497 Merge remote-tracking branch 'origin/release-1.10.1'
# Conflicts:
#	.secrets.baseline
#	src/backend/tests/.test_durations
2026-06-23 16:54:36 -07:00
a66b75ac26 chore: require bumped sdk for lfx release 2026-06-23 16:15:24 -07:00
f85901e49d chore: bump sdk release version 2026-06-23 16:12:43 -07:00
4c8fb6b954 fix: gate release rc setup for stable releases 2026-06-23 16:01:04 -07:00
7db4bcb890 fix: degrade gracefully when caching unpicklable values in RedisCache (#13781)
* fix: degrade gracefully when caching unpicklable values in RedisCache

RedisCache.set only caught pickle.PicklingError, but dill raises other
exception types for inherently unserializable live objects -- a bare
TypeError for an ssl.SSLContext, AttributeError for dynamically-created
pydantic models, etc. None of these subclass PicklingError, so they
escaped the guard and crashed the entire vertex build whenever a flow
with a live LLM client (e.g. ChatGoogleGenerativeAI) ran with
LANGFLOW_CACHE_TYPE=redis.

Serialize in isolation from the network write and treat any
serialization failure as an uncacheable value: log a warning and skip
the cache write rather than raising. A skipped write just means the
value is recomputed on the next access, matching how the in-memory
cache already tolerates such objects. Genuine Redis write failures
(the setex path) still surface.

Fixes #13764

* fix: evict stale Redis entry when skipping an unserializable cache write

The upsert path is get -> merge -> set. When set() now skips an
unserializable value, any previously-cached entry for that key was left
in Redis (up to the 1h TTL), so a subsequent get() could serve stale
data instead of recomputing. Delete the key on the skip path so the
cache correctly reflects 'no valid value'.
2026-06-23 19:02:37 +00:00
8c98cdf5cc fix(security): block socket/urllib network egress in component code scanner (#13784)
* fix(security): block socket/urllib network egress in component code scanner

Completes the CVE-2026-33873 / GHSA-v8hw-mh8c-jxfc fix. The AST scanner
`scan_code_security()` blocked `subprocess` but omitted `socket` and
`urllib`, so LLM-generated / assistant-submitted component code importing
`socket.connect()` or `urllib.request.urlopen()` passed the scan and still
executed server-side during validation — enabling raw-socket reverse
shells, raw exfiltration, and `urllib` SSRF (incl. `file://` local reads
and cloud IMDS credential theft).

Add the network/IPC stdlib attack class to the blocklist (same class as
`subprocess`):
- whole modules: socket, socketserver, ftplib, telnetlib, smtplib,
  poplib, imaplib, nntplib, xmlrpc, pty
- submodules (precise, preserving safe siblings): urllib.request,
  urllib.error, http.client, http.server
- os.dup2 / os.dup attribute calls (socket->shell fd redirection)

High-level HTTP via `requests`/`httpx` stays allowed by design (legit API
components need it), and the safe `urllib.parse` / `from http import
HTTPStatus` siblings remain importable. This scanner is defense-in-depth,
not a full sandbox (see #12787); residual SSRF via the permitted HTTP
clients is unchanged.

Adds regression tests covering each blocked module, the reporter PoC
payloads, and the safe-sibling no-regression cases.

* fix(security): resolve import-alias and wildcard-import scanner bypasses

The component-code scanner matched restricted module members only by the
literal module name, so `import os as o; o.dup2(...)` (alias) and
`from os import *; dup2(...)` (wildcard) slipped past the os.*/sys.*
attribute checks — `os`/`sys` are importable as whole modules, only their
members are restricted.

- track import aliases (incl. `import os.path as p`) and resolve them in
  the attribute-call and attribute-read checks
- track `from <mod> import *` and treat bare references to restricted
  members as direct attribute access (calls via _check_name_call, reads
  via visit_Name), using member sets derived from the existing tables so
  they stay in sync
- collect imports in an order-independent pre-pass

Safe siblings still pass (`o.path.join`, aliased `requests`, wildcard
`getcwd`/`listdir`). Adds regression tests for both bypass patterns plus
no-regression cases.

* fix(security): flag dotted submodule access (urllib.request/http.client)

A bare `import urllib` / `import http` is allowed (the package root is
safe for urllib.parse / http.HTTPStatus), but at runtime the assistant
import chain has already loaded `urllib.request` and `http.client`, so
`import urllib; urllib.request.urlopen(...)` reaches the blocked
submodule without an explicit submodule import and scanned as safe —
re-opening the SSRF / HTTP-client path.

Detect dotted attribute chains that resolve to a blocked submodule in
visit_Attribute (alias-resolved on the root name, exact-match per node to
avoid double-flagging the chain). Catches the no-import form too (pure
runtime-preload reliance) and `import urllib as u; u.request...`.

Safe siblings still pass: urllib.parse.*, http.HTTPStatus, os.path.*.
Adds regression tests for the bare-import and alias bypass variants.
2026-06-23 18:54:38 +00:00
f58914bc84 fix: release candidate version selection (#13776)
* fix: share release candidate version selection

* fix: review comments addressed
1.10.1rc3
2026-06-22 15:35:10 -07:00
2302ef9931 fix(component): reconnect stale cached SQL database connections on SQL Database component (#13733)
* Implement check for stale cached database connection

Add stale connection check for cached database.

* [autofix.ci] apply automated fixes

* fix(component): use SQLAlchemy pre-ping for SQL database cache

* [autofix.ci] apply automated fixes

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Eric Hare <ericrhare@gmail.com>
2026-06-22 14:10:12 -07:00
408b737980 fix(agents): stop leaking LangChain chain markers to stdout (#13662) (#13730)
* fix(agents): stop leaking LangChain chain markers to stdout

The Agent verbose input defaulted to True and was passed straight to
LangChain's AgentExecutor, which attaches a StdOutCallbackHandler that
prints "> Entering new ... chain" / "> Finished chain." via print() --
bypassing Langflow's logging entirely and flooding container/k8s/OpenShift
stdout logs on every Agent execution, even with LANGCHAIN_VERBOSE=false.

Gate the markers on the LANGCHAIN_VERBOSE env var (off by default) via a new
resolve_agent_verbose() helper, wired into the legacy AgentExecutor paths
(base LCAgentComponent.run_agent/get_agent_kwargs, ALTK base agent, CSV
Agent). The env var is the conventional LangChain switch; set
LANGCHAIN_VERBOSE=true to restore the markers. The component verbose input
no longer attaches the stdout handler on its own. Agent steps remain visible
in the UI via the existing event stream.

Restamps the CSV Agent component-index code_hash + index sha256.

Fixes #13662

* fix(agents): surface LANGCHAIN_VERBOSE gating in the verbose input info (#13662)

Address review I1: after gating stdout chain markers on LANGCHAIN_VERBOSE,
the legacy AgentExecutor agents still showed a Verbose toggle whose value is
no longer read, so the UI control silently did nothing. Add an info string to
LCAgentComponent's shared verbose BoolInput explaining the markers are now
gated on LANGCHAIN_VERBOSE (off by default) and the toggle no longer attaches
LangChain's stdout handler, so the UI and behavior agree.

The main Agent already drops this input (create_agent path); the change
surfaces in the legacy LCAgentComponent agents that inherit get_base_inputs()
(CSV/JSON/SQL/XML/OpenAPI/OpenAI Tools/Tool Calling/Vector Store Router + Cuga).
Restamps the component index for those 9 inherited entries.

* chore: auto-bake note keys and regenerate backend locales/en.json [skip ci]

* Update component_index.json

* [autofix.ci] apply automated fixes

* [autofix.ci] apply automated fixes (attempt 2/3)

* [autofix.ci] apply automated fixes

* fix(agents): document ALTK verbose env gate

* [autofix.ci] apply automated fixes

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2026-06-22 13:53:02 -07:00
0300305dc5 docs: fix empty see also sections in extensions (#13774)
* docs: fix empty see also sections in extensions

* docs: update 1.10x version
2026-06-22 13:22:53 -07:00
f39416b806 fix: make IBM WatsonX models selectable and runnable in the Langflow Assistant (#13771)
* fix ibm models integration on assistant

* update assistant docs

* [autofix.ci] apply automated fixes

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2026-06-22 19:55:48 +00:00
0125c386ac fix(i18n): fix MCP hardcoded strings (#13621)
* fix(i18n): wrap hardcoded MCP auto-install strings with t()

Adds mcp.serverNotRunning, mcp.installDisabledWarning, mcp.installTooltip,
and mcp.failedToInstall keys to en.json and replaces the hardcoded strings
in McpAuthSection, McpAutoInstallContent, and useMcpServer. Downloads
updated translations for all 6 locale files from GP.

* fix(i18n): wrap hardcoded playground no-input strings with t()

Adds playground.runFlow and playground.noInputHint keys to en.json and
replaces hardcoded strings in both no-input.tsx components (playgroundComponent
and IOModal). Reuses flowBuild.stop for the Stop button. Uses Trans for the
hint paragraph with embedded Chat Input link; works around react-i18next v16
conditional type by casting to React.FC<TransProps<string>>.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(i18n): route welcome-screen template apply through resetFlow for translations

useApplyTemplateToCurrentFlow was calling setNodes() / setEdges() directly
and then useFlowStore.setCurrentFlow() (metadata-only), bypassing the
resetFlow → syncNodeTranslations pipeline. This meant component display names
and descriptions were never translated to the active language when a user
selected a starter template from the welcome overlay.

Fix: use useFlowsManagerStore.setCurrentFlow() instead, which calls resetFlow
and therefore syncNodeTranslations with the already-loaded typesStore data.
Keep the direct setNodes/setEdges path as a fallback when currentFlow is null.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore(i18n): sync all locale files from GP

Frontend: adds playground.runFlow and playground.noInputHint translations
for all 6 locales (new keys from no-input component i18n work).

Backend: adds template_notes.vector_store_rag.e8deaec6 (updated README note
with docs.langflow.org URL replacing localhost), drops orphaned keys for
renamed/removed components (chunkdoclingdocument, doclinginline) that no
longer exist in en.json.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* [autofix.ci] apply automated fixes

* fix: guard saveFlow rollback against stale flow reference

Only restore the previous flow on save failure if the user hasn't
already navigated to a different flow, preventing the rollback from
clobbering a newer active selection.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(tests): fix CI failures in use-apply-template and no-input tests

- Fix double-renamed declaration setCurrentFlowInManagerInManager → setCurrentFlowInManager
- Fix mock state key from setCurrentFlowInManager to setCurrentFlow (matches hook's store selector)
- Update test assertions to check setCurrentFlowInManager instead of setNodes/setEdges (hook routes through manager store when currentFlow exists)
- Enhance jest.setup.js Trans mock to parse <N>text</N> i18nKey tags and render React elements from components prop (fixes "Add a" text not found in no-input test)

* fix(frontend): fix Biome import order in playground no-input component

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2026-06-22 18:48:09 +00:00
47760d22fb fix(ci): drop macOS Intel from py3.14 cross-platform set (onnxruntime x86_64 wheel gap) (#13773)
fix(ci): drop macOS Intel from py3.14 cross-platform set (onnxruntime x86_64 gap)

Follow-up to #13772. The new Python 3.14 experimental job on macOS Intel
(macos-latest-large / x86_64) fails at dependency resolution:

  No solution found ... onnxruntime>=1.24.1 has no wheels with a matching
  platform tag (macosx_*_x86_64) ... onnxruntime>=1.17.0,<=1.23.2 has no
  cp314 ABI ... lfx==...rc0 depends on markitdown -> magika -> onnxruntime ...
  unsatisfiable.

This is a permanent macOS x86_64 ecosystem gap, not the find-links bug:
- langflow pins `onnxruntime>=1.26; python_version>='3.14'` (pyproject.toml), and
- onnxruntime dropped macOS x86_64 wheels at 1.24, while the older onnxruntime
  that still ships macOS x86_64 wheels (<=1.23.2) has no cp314 wheels.

So macOS Intel + py3.14 can never resolve. macOS Intel resolves fine through
3.13 (the py<3.14 branch pins onnxruntime<1.24, which still has x86_64 wheels);
macOS arm64 has cp314 wheels and is unaffected. The job is non-blocking
(continue-on-error) so it never blocked releases, but it's a guaranteed,
no-signal failure burning the costly macos-latest-large runner every release.

Drop macOS Intel from the 3.14 experimental matrix (keep linux/windows/macOS-arm64)
and update the docs/comments to explain the x86_64 wheel gap.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
1.10.1.rc0
2026-06-22 11:44:51 -07:00
b0d4fe0645 fix(ci): resolve pre-release cross-platform install failure; graduate py3.13, add py3.14 experimental (#13772)
fix(ci): resolve pre-release cross-platform install + graduate py3.13, add py3.14

The cross-platform install test failed *only* on "Pre-release" release runs,
in the Python 3.13 (Experimental) jobs, while normal releases passed.

Root cause: the experimental job's "Force reinstall local wheels to prevent
downgrades" step drops `--no-deps` when `pre_release=true` (to allow pre-release
dependency resolution) but never passed `--find-links` to the local wheel dirs.
uv then re-resolved langflow-base's `lfx>=X.Y.Zrc0,<X.(Y+1).dev0` constraint
against PyPI only, where the matching rc/dev wheel is not yet published, and
failed with "No solution found when resolving dependencies ... unsatisfiable".
Stable releases keep `--no-deps`, never re-resolve, and so never hit it.

Fix: build a `FIND_LINKS` array over the local wheel dirs (sdk/lfx/base/bundles)
and thread it into both force-reinstall `uv pip install` commands (Windows and
Unix). Harmless on the stable `--no-deps` path; required on the pre-release path.

Also:
- Graduate Python 3.13 from the experimental matrix to the stable (blocking)
  matrix on linux amd64, macOS arm64, and windows amd64. macOS Intel
  (macos-latest-large) stays at 3.12 only on the blocking matrix to limit use of
  the costly runner, matching the existing 3.10/3.12 design.
- Add Python 3.14 as the new experimental (non-blocking, continue-on-error) set,
  mirroring the platform coverage 3.13 previously had (incl. macOS Intel).
- Update the test-summary messages and cross-platform-test.md accordingly.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 11:06:40 -07:00
cc398d94a6 fix(lfx): make OpenDsStar optional (#13749) 2026-06-22 09:15:05 -07:00
b8d7a1a534 fix(frontend): stop browser autofill from corrupting component config fields (#13748)
* fix(frontend): stop browser autofill from corrupting component config fields

Chrome (and password managers) ignore autocomplete="off" for fields they
heuristically treat as credentials. A node's API-key (type=password) field
makes Chrome classify the config panel as a login form and inject a saved
username (e.g. "admin") into adjacent text/name fields plus a saved password
into the key field. Langflow autosaves, so merely opening a node and clicking
a field can silently overwrite and persist the injected values, corrupting the
flow — across users, including ones who never logged into the instance.

Suppress autofill on node-config inputs by default:
- Base Input/Textarea primitives now emit `new-password` (secret fields) / `off`
  plus the 1Password/LastPass/Bitwarden/Dashlane opt-out data-* attributes.
  `new-password` also breaks Chrome's username-pairing heuristic, so adjacent
  text/name fields (e.g. the component name) stop being filled.
- The popover (API-key/secret + str) and TextAreaComponent secret fields key
  the token on the field's secret-ness, not the live `type`, so revealing a
  masked value does not re-arm autofill.
- Real credential forms (login / signup / admin login) opt back in via a new
  `allowAutofill` prop, preserving wanted password-manager autofill. Auth-form
  `name` attributes (used by Playwright selectors) are untouched.

Adds unit tests locking the suppressed/opt-in attribute contract.

Known low-risk gaps deferred to follow-up (not credential-like, not the reported
vector): ag-grid table cell editors, numeric inputs, file-path input, Ace editor.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* Update src/frontend/src/components/ui/input.tsx

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* fix(frontend): repair malformed autofillProps after suggestion merge

The accepted CodeRabbit suggestion on the Input primitive left a duplicated,
dangling object-literal fragment after the autofillProps statement, a syntax
error that broke the frontend build (and therefore all CI). Remove the
duplicate so the intended branch logic stands: an allowAutofill field uses a
caller-provided autoComplete when given, otherwise emits no autocomplete
attribute (browser default); suppressed fields keep new-password/off + the
password-manager opt-outs.

Updates the unit test to the refined contract (allowAutofill without an
explicit autoComplete emits no attribute) and adds coverage for an explicit
autoComplete on an opted-in field.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(frontend): suppress autofill in ag-grid table cell editors

ag-grid mounts its cell editor <input>/<textarea> outside React when a cell
enters edit mode, bypassing the hardened Input/Textarea primitives. Editable
table cells (TableNodeComponent -> TableModal -> shared TableComponent, plus
global variables and other editable grids) were therefore still autofillable,
and autosave would persist any injected value.

Add an onCellEditingStarted handler on the shared TableComponent that stamps
autocomplete="off" + the password-manager opt-out data-* attributes onto the
active editor (inline and large-text popup editors) via a small reusable
suppressAutofillOnElement helper. Closes the last node-config autofill vector
called out as deferred in the original fix.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
2026-06-22 08:40:30 -07:00
309a558b55 chore(deps): bump toolguard floor to 0.2.20 (#13769)
toolguard 0.2.20 ships the Windows build-time code-generation fixes from
AgentToolkit/toolguard#28 (repr()-embedded paths in the pytest runner,
utf-8 file reads, and pyright executable resolution). Raising the floor
from 0.2.16 to 0.2.20 guarantees Windows users get the working green-loop
codegen rather than the silent stub fallback, completing the langflow-side
Policies/ToolGuard Guard-mode fix (#13751).

The existing toolguard.runtime circular-import structure is unchanged in
0.2.20, so lfx's _warm_circular_imports() helper still applies.
2026-06-22 08:40:10 -07:00
f5ba8f54ca fix: handle Windows path separators in Policies ToolGuard Guard mode (#13727) (#13751)
* fix: handle Windows path separators in Policies ToolGuard Guard mode (#13727)

Guard mode read generated guard files back from the node template with
str(file_name), where file_name is a pathlib.Path from the toolguard
result model. On Windows str() yields backslashes, but the files are
stored (by sync_generated_guard_code_inputs) under their POSIX relative
path via Path.as_posix() (forward slashes). Every lookup therefore missed
on Windows, attrs.get(...) returned None, and None["value"] crashed with
"'NoneType' object is not subscriptable".

- Add PoliciesComponent._template_field_key() to normalize a file name to
  the POSIX key the sync step writes; route all reads in
  make_toolguard_result() through it so lookups match on every platform.
- Raise a clear "re-run Generate" error when a generated field is missing
  instead of subscripting None.
- Relax validate_before_generate(): api_key is optional (required=False,
  advanced=True) and credentials can come from the model connection/env,
  so only the model selection is required. Fixes the spurious
  "model or api_key cannot be empty!" block.
- Regenerate the bundled component index for the updated source.

The separate "Generate emits the pass # FIXME stub" and Windows
charmap-decode defects are in the upstream toolguard package (latest
0.2.19) and are out of scope here; this component is ready to consume a
fixed toolguard release once available.

Fixes #13727

* [autofix.ci] apply automated fixes

* Update templates

* [autofix.ci] apply automated fixes

* fix: address review feedback on Policies component

- Add `str | Path` type hints to `_template_field_key` and the inner
  `read_content` helper (mypy compliance).
- Give the empty-template `ValueError` in `make_toolguard_result` a
  descriptive message instead of a bare raise.
- Regenerate the bundled component index for the updated source.

* Update component_index.json

* [autofix.ci] apply automated fixes

* [autofix.ci] apply automated fixes (attempt 2/3)

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2026-06-19 16:15:42 -07:00
9bd47b601f Template and comp update 2026-06-19 15:38:27 -07:00
2b4e720e36 fix: stop temp_dirs masking startup errors; fail-fast on unresolvable SQLite path (#13634) (#13729)
fix: fail-fast on unresolvable SQLite path; stop temp_dirs masking startup errors

Addresses #13634 (Bug 2 in full; Bug 1 diagnostics + docs — relative-path
normalization deferred to a separate design decision per maintainer triage).

- main.py: bind `temp_dirs = []` before the lifespan `try` so the shutdown
  `finally` cleanup never raises UnboundLocalError and masks the real startup
  error when failure occurs before bundle loading.
- database/service.py: add get_sqlite_database_file_path() and
  check_sqlite_database_path(), surfacing an actionable error (resolved path,
  CWD anchoring, absolute-path guidance) when a SQLite DB's parent directory is
  missing — instead of the opaque "Error creating DB and tables". Diagnostics
  only: no path normalization, no directory creation, no rejection of
  currently-working relative paths.
- database/utils.py: run the check in initialize_database() before creation.
- docs: note that SQLite LANGFLOW_DATABASE_URL paths should be absolute.
- tests: regression coverage for both bugs.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 15:29:51 -07:00
271c7506bd fix(lfx): preserve custom static models through models.dev override (#13676)
Custom models added to the bundled *_constants.py lists (e.g.
openai_constants.py) were silently discarded once a models.dev snapshot
was active. apply_models_dev_overrides() replaced a covered provider's
entire static group with the models.dev rows and skipped any later
same-provider group (the OpenAI embeddings group), so user-added LLM and
embedding entries never reached get_unified_models_detailed() and the
model/embedding pickers.

Fold any static entry whose name models.dev does not cover into that
provider's override list (mutating the same list object that is appended
to the result, so a later embeddings group folds into it too). models.dev
still wins for every name it does cover, preserving its fresher metadata.

This is especially important for embeddings: the embedding dropdown has no
free-text combobox, so a custom embedding must be present in the list to
be selectable.

Fixes #13556

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 15:28:38 -07:00
db2f5c447e fix(smart-router): stop unselected branches from executing downstream nodes (#13536)
* fix(smart-router): stop unselected branches from executing downstream nodes

Smart Router only marked unselected branches INACTIVE via stop(), but that
state is reset between scheduling passes. When two routes reconverge on a
shared downstream node (e.g. both feeding one Chat Output/agent), the
re-activated unselected branch was picked up again through that shared node
and executed -- producing extra/duplicate provider calls and running paths
that should have been skipped.

Smart Router now also records a *persistent* conditional exclusion for every
unselected output (mirroring the If-Else ConditionalRouter), so unselected
branches stay excluded for the whole run while the matched branch -- and any
shared downstream node reachable from it -- still runs.

Because Smart Router keeps a single matched output and must exclude all the
others, add Graph.exclude_branches_conditionally(), which excludes several
output branches from one source vertex at once without clearing siblings
(the single-branch exclude_branch_conditionally clears prior exclusions per
call). Exclusions accumulate across process_case/default_response calls.

Adds a graph-level regression test covering separate-leaf, reconverging, and
three-route topologies. Regenerates the SmartRouter component_index entry.

Fixes #13440

* fix(smart-router): keep shared downstream nodes runnable

* fix(graph): avoid branch traversal through feedback cycles

* test(graph): cover If-Else branch reconvergence; DRY conditional exclusion

Addresses PR review feedback on #13536:
- Add an If-Else (ConditionalRouter) reconvergence regression test guarding the
  shared exclude_branch_conditionally path (RED before the fix, GREEN after); it
  surfaces a latent If-Else bug the same fix resolves.
- DRY exclude_branch_conditionally / exclude_branches_conditionally via a shared
  _replace_conditional_exclusions helper; the single-branch method delegates to
  the multi-branch one (strictly behavior-preserving).
- Document the conditionally-excluded-vertex guard in vertex_types._get_result.
- Narrow the test module import guard from Exception to ImportError.

* fix(graph): drop excluded predecessor from list inputs; cover Else routing

Addresses PR review feedback on #13536:
- Skip a conditionally-excluded, unbuilt predecessor when building an
  is_list=True input so it contributes nothing instead of injecting the input's
  template default (a stray empty element next to the real branch's value). RED
  before the fix via a new list-merge regression test.
- Add graph-level Smart Router Else coverage: a matched route excludes the Else
  branch (its leaf stays unrun while the matched leaf and merge still run); no
  match runs the Else branch while the category branches stay unrun.
- Drop the module-level try/except ImportError -> pytest.skip guard in the test
  module in favor of direct imports, matching the sibling graph tests.

* test(graph): cover Smart Router outputs reconverging directly on one node

LE-1427 / Alice's report: both router outputs connected to the same
downstream component. Existing reconvergence tests route through
intermediate nodes; this covers the router as the immediate predecessor
of the shared node, where the merge is reachable from a sibling output
of the router itself. RED without the sibling-output protection in
exclude_branches_conditionally, GREEN with it.

* Fix the conditional branch

* Update component_index.json

* [autofix.ci] apply automated fixes

* [autofix.ci] apply automated fixes

* [autofix.ci] apply automated fixes (attempt 2/3)

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2026-06-19 15:00:34 -07:00
eb2473914e fix(docker): force HTTP/1.1 on frontend nginx upstream proxy (#9010) (#13725)
fix(docker): force HTTP/1.1 on frontend nginx upstream proxy

The frontend nginx config proxied to the backend without setting
proxy_http_version, so nginx used its default of HTTP/1.0 for the
upstream requests. Behind an HTTP/1.1-only proxy (e.g. an Istio/Envoy
service-mesh sidecar) those requests are rejected with HTTP 426 Upgrade
Required, breaking /api, /health, and /health_check.

Set proxy_http_version 1.1 explicitly on every proxy_pass block in both
default.conf.template (rendered into the production frontend image) and
nginx.conf. This is version-independent and keeps working regardless of
which nginx the image ships (the image pins nginx 1.28.0, whose upstream
default is still HTTP/1.0).

Verified with the pinned nginx image in front of an HTTP/1.1-only mock
upstream: /api, /health, /health_check return 200 and nginx forwards
upstream as HTTP/1.1 (was 426 / HTTP/1.0 before the fix).

Fixes #9010
2026-06-19 14:59:03 -07:00
194f42be81 perf: cache Ollama model capabilities to fix slow Cloud model toggles (#13722)
* perf: cache Ollama model capabilities to fix slow Cloud model toggles

Enabling/disabling models for an Ollama provider pointed at the cloud
base URL (https://ollama.com) lagged on every toggle, while a local
Ollama stayed instant (issue #12399).

Root cause: get_ollama_models() probes capabilities with one POST
/api/show per model on top of the GET /api/tags listing, so a single
catalog read costs N + 1 upstream requests. GET /enabled_models reads
both llm and embeddings (replace_with_live_models(model_type=None)), so
each read paid 2 x (N + 1), and every model toggle triggers a refetch.
On Ollama Cloud's large public catalog over the internet this crawls;
local Ollama has a tiny catalog and ~0 latency so it stays fast. The
existing 30s list cache only helps clustered same-capability reads.

Fix: add a per-model capability cache keyed by (base_url, model_name).
A model:tag's capabilities are intrinsic to the model, so the /api/show
result is reused instead of re-probed. This makes the llm and embedding
reads share a single fan-out (N probes, not 2N) and makes any read after
the short list-TTL re-probe only models never seen before, not the whole
catalog. TTL is bounded at 10 minutes so a re-pull that changes a tag's
capability class self-heals quickly. A probe failure is still absorbed
and left uncached so one bad model never poisons or sticks in the
catalog.

Backend-only and behavior-preserving: the model lists returned are
identical; only the upstream request count drops.

* [autofix.ci] apply automated fixes

* [autofix.ci] apply automated fixes (attempt 2/3)

* perf: avoid caching empty Ollama capabilities and prune stale entries

Address review feedback on the per-model capability cache (#13722).

A 200 /api/show with no capabilities resolved to [] and was cached for the full TTL, while a real RequestError/HTTPStatusError was correctly left uncached, so a transient empty response could hide a model from the picker for 10 minutes. Cache only a populated capability list, so an empty/absent array is re-probed on the next read like the error path. Real Ollama always returns a populated array, so no legitimately-capable model is ever re-probed.

The capability cache expired on read but never evicted, retaining one entry per distinct model seen for the process lifetime. Prune it to the live catalog on each fresh read so it tracks the current catalog; entries for other base URLs are untouched.

Adds behavioral tests for both paths.

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2026-06-19 19:37:17 +00:00
4ebac3f863 chore: upgrade langchain (#13750)
upgrade langchain
2026-06-19 18:43:48 +00:00
81196ceabd fix: validate uploaded MCP servers config to close command-injection path (#13709)
* fix: validate uploaded MCP servers config to close command-injection path

The `_mcp_servers_<uid>.json` file-upload path stored the raw uploaded
bytes as the user's MCP servers config without validation. Those
`command`/`args` are later spawned via the MCP stdio transport, so an
authenticated user could upload a config with an arbitrary command and
have it executed on the server — bypassing the command allow-list that
the structured `/api/v2/mcp/servers` endpoints already enforce via
`MCPServerConfig` (CWE-77 / CWE-94).

This wires the same `MCPServerConfig` validation into the file-upload
branch: the uploaded JSON must be an object with an `mcpServers` map, and
every entry is validated against the allow-list before anything is
written to storage. The file is rewound so the subsequent save re-reads
the original bytes.

Adds a regression test asserting a disallowed command is rejected with
422 and nothing is persisted, and updates the existing replace test to
use an allow-listed command.

* [autofix.ci] apply automated fixes

* [autofix.ci] apply automated fixes (attempt 2/3)

* Update test_mcp_servers_file.py

* test: cover MCP config validator reject branches + close upload lock bypass

Address review on the MCP-config-upload validation fix:

- Enforce the MCP-servers lock on the /api/v2/files upload path. The
  structured /api/v2/mcp/servers endpoints 403 non-superuser writes when
  mcp_servers_locked is on, but the upload branch writes the same
  _mcp_servers_<uid>.json that get_server_list reads, so without the same
  guard a non-superuser could replace their MCP config while locked.
- Pin the validator's own reject branches: invalid-JSON (422) and a
  non-object 'mcpServers' value (422), neither persisting anything.
- Add lock-guard coverage: blocked for a locked non-superuser (403), still
  allowed for a superuser.

* test: update MCP upload replacement fixture

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2026-06-19 18:37:48 +00:00
0b14a6b06b fix(security): gate ToolGuard guard-code execution on allow_custom_components (#13701)
* fix(security): gate ToolGuard guard-code execution on allow_custom_components

The Policies/ToolGuard component executes guard Python whose source is taken
from the component's client-editable CodeInput template values
(make_toolguard_result reads attrs[...]["value"]) and exec'd via
load_toolguards_from_memory. Those values are not covered by the
custom-component hash gate, so an authenticated user could run arbitrary backend
Python even with allow_custom_components=false (stored exec / cross-tenant).

guard_tools() now refuses to run when allow_custom_components is False, before
importing or loading any toolguard runtime, so the client-supplied guard code is
never exec'd. When no settings service is present (lfx standalone), execution is
allowed as a local/trusted context.

* [autofix.ci] apply automated fixes

* [autofix.ci] apply automated fixes (attempt 2/3)

* fix(security): fail closed when settings service is unavailable

Address review feedback on the ToolGuard exec gate:

- _code_execution_allowed() now denies execution when the settings layer
  is present but the service returns None, matching the behavior of
  validate_flow_for_current_settings (which raises in that case). Fail-open
  is reserved for the truly standalone path where the settings layer cannot
  be imported at all (lfx used as a bare library).
- Strengthen the blocked-path test to assert the toolguard runtime is never
  imported when execution is refused.
- Add a test pinning the allow side: allow_custom_components=True must reach
  _import_toolguard, so a future flip of the default is caught.

* Update component_index.json

* [autofix.ci] apply automated fixes

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2026-06-19 11:07:56 -07:00
7799d1a9c1 fix(security): scope voice-mode clients per user (#13702)
* fix(security): scope voice-mode clients per user

Voice mode held an ElevenLabs client in a process-global singleton
(ElevenLabsClientManager) that cached the FIRST caller's API key and returned it
to every subsequent user - billing all tenants' TTS to one account and exposing
that account's voice library. The OpenAI/voice config caches were keyed only by
the client-supplied session_id, so two users sharing a session_id reused each
other's TTSConfig (and its OpenAI client built from the other user's key).

- Replace the singleton: get_or_create_elevenlabs_client now builds a fresh
  client from the requesting user's own key on each call.
- Key voice_config_cache / tts_config_cache by (user_id, session_id); thread the
  authenticated user id through get_voice_config/get_tts_config/get_create_response
  and resolve voice config only after authentication.

* [autofix.ci] apply automated fixes

* [autofix.ci] apply automated fixes (attempt 2/3)

* fix(security): guard failed auth in flow_tts_websocket; type cache keys as UUID

Address review feedback on #13702:
- Add current_user/openai_key None guard after authenticate_and_get_openai_key
  in flow_tts_websocket, mirroring flow_as_tool_websocket, so a failed auth
  returns early instead of raising AttributeError on current_user.id.
- Annotate the voice/TTS config cache keys (and get_voice_config/get_tts_config
  user_id params) as UUID | None, matching the User.id passed by every caller.

* fix: migrate voice_mode off deprecated websockets legacy client API

websockets.connect() resolves to the asyncio implementation under the pinned
15.x, so the SendQueues annotations were both deprecated and the wrong runtime
type (the object is a ClientConnection, not the legacy WebSocketClientProtocol).
On the same path the legacy extra_headers kwarg was a latent runtime bug: the
asyncio connect takes additional_headers, and the old name is forwarded to
BaseEventLoop.create_connection() and raises TypeError at connect time.

- Import websockets.asyncio.client.ClientConnection and use it for the
  openai_ws annotations on SendQueues.
- Change extra_headers -> additional_headers at both connect() call sites.

Swept src/ -- both patterns were isolated to voice_mode.py.

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2026-06-19 10:29:57 -07:00
2754c84aad fix(security): gate PythonREPL execution on allow_custom_components (GHSA-8qpj-27x8-pwpq) (#13700)
* fix(security): gate PythonREPL execution on allow_custom_components (GHSA-8qpj-27x8-pwpq)

The Python Interpreter component (PythonREPLComponent) and the legacy Python
REPL tool execute arbitrary user-supplied Python. They are sanitized and blocked
on the unauthenticated public path, but an authenticated user could run code
even in deployments locked down with allow_custom_components=false.

Add ensure_code_execution_enabled(), which refuses execution when
allow_custom_components is False (consistent with the custom-component policy),
and call it from both components before any sanitize/exec. When no settings
service is present (lfx standalone CLI), execution is allowed as a local/trusted
context.

* [autofix.ci] apply automated fixes

* [autofix.ci] apply automated fixes (attempt 2/3)

* Update src/lfx/src/lfx/components/utilities/python_repl_core.py

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* fix: broad exception to import error

* [autofix.ci] apply automated fixes

* fix(security): fail closed on null settings service in code-exec gate

Address review feedback on the PythonREPL exec gate (GHSA-8qpj-27x8-pwpq):

- ensure_code_execution_enabled() now fails closed when get_settings_service()
  returns None (registered-but-failed stack), matching the sibling
  validate_flow_for_current_settings. get_service swallows init errors into
  None, so the previous return-allow could silently bypass the gate.
- Add per-component tests asserting run_python_repl and run_python_code refuse
  execution when allow_custom_components=False, locking in the gate wiring.
- Add a gate test that a non-ImportError from get_settings_service() propagates
  rather than failing open; flip the None case to assert fail-closed.
- Drop a duplicated comment pair in python_repl_core.py and resync the embedded
  copy + sha256 in component_index.json.

* [autofix.ci] apply automated fixes

* Update component_index.json

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
2026-06-19 10:29:17 -07:00
11a18f7834 fix(security): disable credentials for wildcard CORS origins (CWE-942) (#13708)
* fix(security): disable credentials for wildcard CORS origins (CWE-942)

Langflow defaults to cors_origins="*" with cors_allow_credentials=True. With a
wildcard origin AND credentials, Starlette reflects the caller's Origin and
returns Access-Control-Allow-Credentials: true, so any website can make
credentialed cross-origin requests on behalf of a logged-in user (CSRF /
credential theft).

Force allow_credentials=False at the CORS middleware whenever the configured
origin list is a wildcard. Specific (non-wildcard) origins continue to allow
credentials, so properly-configured multi-origin deployments are unaffected.

* [autofix.ci] apply automated fixes

* [autofix.ci] apply automated fixes (attempt 2/3)

* fix(security): warn when wildcard CORS origin disables credentials

Share a single cors_origins_contain_wildcard() predicate between
warn_about_future_cors_changes and the middleware CORS config so the two
wildcard checks can't drift, and log an explicit warning when a wildcard
origin forces allow_credentials off.

Previously the list-form wildcard (e.g. LANGFLOW_CORS_ORIGINS="https://app.com,*"
-> ["https://app.com", "*"]) disabled credentials silently: the
permissive-defaults warning only matched the bare "*" string, so an operator
who set credentials on purpose got no log line pointing at the wildcard. The
shared predicate now catches every wildcard shape, and the middleware surfaces
the override (only when credentials were actually on) so the cause is visible.

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2026-06-19 10:22:46 -07:00
b43d17bca9 Update test_redis_job_queue_service.py 2026-06-18 16:56:02 -07:00
25124facde fix(security): sanitize APIRequest Content-Disposition filename (GHSA-h3c6-fqr4-m99p) (#13695)
* fix(security): sanitize APIRequest Content-Disposition filename (GHSA-h3c6-fqr4-m99p)

When Save to File was enabled, the APIRequest component derived the output
filename from the HTTP response's Content-Disposition header without sanitizing
path separators, so a malicious/compromised endpoint returning
filename="../../../../tmp/evil.sh" could write the response body outside the
component temp dir (arbitrary file write -> potential RCE).

Reduce the header-supplied filename to its basename (Path(...).name) and add a
defense-in-depth check that the resolved path stays within the temp dir.

* [autofix.ci] apply automated fixes

* [autofix.ci] apply automated fixes (attempt 2/3)

* fix: coderabbit hardening

* [autofix.ci] apply automated fixes

* fix(security): address review on Content-Disposition traversal fix

Apply @ogabrielluiz review feedback on the APIRequest Content-Disposition
filename sanitization (GHSA-h3c6-fqr4-m99p):

- Use file_path.resolve().is_relative_to(component_temp_dir.resolve()) for
  the defense-in-depth containment check, matching the ~18 other call sites
  (filesystem.py, base_file.py, storage/local.py) instead of the lone
  .parents-based containment expression. Available on the 3.10 floor.
- Strip NUL bytes from the header filename so filename="evil\x00.sh" fails
  cleanly as the intended basename instead of raising the cryptic
  "embedded null character in path" ValueError from a later .resolve().
- Add a NUL-byte regression test.

Regenerate the embedded component code + code_hash in component_index.json
and the Pokédex Agent starter project (e1a05b25f31c -> 71438e194131).

* [autofix.ci] apply automated fixes

* Update component_index.json

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2026-06-18 14:53:20 -07:00
0f7c220b3d fix(security): HMAC-sign RedisCache payloads before dill deserialization (#13698)
* fix(security): HMAC-sign RedisCache payloads before dill deserialization

RedisCache.get() called dill.loads() directly on bytes read from Redis. dill
executes embedded reduce gadgets, so anyone able to write under the
langflow:cache: namespace (a co-tenant on a shared Redis, an exposed/un-ACL'd
port, etc.) could plant a payload that runs arbitrary code in the Langflow
process on the next get() (CWE-502, RCE).

Cache values are now prefixed with an HMAC-SHA256 tag derived from the server
SECRET_KEY. get() verifies the tag with hmac.compare_digest before
deserializing; unsigned/tampered/legacy entries are treated as a cache miss and
never passed to dill.loads(). Only active when LANGFLOW_CACHE_TYPE=redis.

* [autofix.ci] apply automated fixes

* [autofix.ci] apply automated fixes (attempt 2/3)

* Update src/backend/base/langflow/services/cache/service.py

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* fix: redis payload rejection

* test(cache): cover short-payload integrity short-circuit; align settings deps import

Address review feedback on the RedisCache HMAC integrity fix:
- Import get_settings_service from langflow.services.deps to match the rest
  of services/ and keep the non-optional SettingsService return type (the lfx
  variant is SettingsServiceProtocol | None and the next line dereferences
  .auth_settings without a guard).
- Add test_get_rejects_payload_shorter_than_tag covering the
  len(value) < _HMAC_DIGEST_SIZE short-circuit in get() (a 1-byte write under
  the namespace is the cheapest attacker input and was previously uncovered).

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
2026-06-18 14:38:10 -07:00
744c36e581 fix(security): stop issuing 365-day superuser token via auto_login (GHSA-fjgc-vj2f-77hm) (#13699)
* fix(security): stop issuing 365-day superuser token via auto_login (GHSA-fjgc-vj2f-77hm)

AUTO_LOGIN defaults on, so an unauthenticated GET /api/v1/auto_login minted a
365-day superuser access token (no refresh). That is a year-long superuser
bearer token handed out without credentials.

create_user_longterm_token now issues normally-scoped tokens (short-lived access
+ refresh, via create_user_tokens), and the auto_login route sets the refresh
cookie so the dev session refreshes seamlessly instead of relying on a year-long
token. A loud warning is logged whenever auto_login issues a session. AUTO_LOGIN
default is intentionally left True (flipping it is breaking and is staged for
v2.0); this bounds the blast radius without changing the dev UX.

* [autofix.ci] apply automated fixes

* [autofix.ci] apply automated fixes (attempt 2/3)

* Update src/backend/tests/unit/services/auth/test_auth_service.py

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* fix(security): refresh short-lived auto-login token on the frontend

The auto_login route now issues a short-lived access token plus a
refresh_token_lf cookie (GHSA-fjgc-vj2f-77hm), but the frontend only
armed the proactive refresh interval for manual sessions (!autoLogin).
Under default AUTO_LOGIN a tab left open past ACCESS_TOKEN_EXPIRE_SECONDS
would 401 with no client-side recovery until a full page reload.

Arm the proactive /refresh interval for auto-login sessions too so the
session consumes the new refresh cookie and stays alive transparently.
Auto-login skips the redundant on-mount refresh since /auto_login just
minted a fresh token. Adds a ProtectedRoute regression test.

* [autofix.ci] apply automated fixes

* starter templates update

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
2026-06-18 14:37:25 -07:00
17fd60e929 fix: downgrade torchvision until cpu index fix (#13745)
* fix: downgrade torchvision until cpu index fix

downgrade torchvision until cpu index fix. currently latest is 0.27.1+df56172

but we want 0.27.1 and 0.27.1+cpu
d is viewed as a new version than c

* chore: add comment

add comment
2026-06-18 17:17:03 -04:00
e7c33dbaad fix(security): do not execute code in validate_code (GHSA-2wcq-pvw2-xh7v) (#13696)
* fix(security): do not execute code in validate_code (GHSA-2wcq-pvw2-xh7v)

POST /api/v1/validate/code compiled and exec'd every function definition in the
submitted code as part of "validation". Executing a function definition
evaluates its decorators and default-argument expressions at definition time, so
a payload like

    def f(x=__import__("os").system("...")): ...

runs arbitrary code during validation without the function ever being called -
an authenticated RCE (effectively unauthenticated under the default AUTO_LOGIN
single-user setup). The same issue was also reported separately as a duplicate.

validate_code now compiles each function only to surface syntax/compile errors
and never exec()s it. The legitimate component-execution paths
(create_function/execute_function/eval_function), which are separately gated by
allow_custom_components, are unchanged.

* [autofix.ci] apply automated fixes

* [autofix.ci] apply automated fixes (attempt 2/3)

* refactor(security): drop dead _create_langflow_execution_context helper

Addresses review feedback on GHSA-2wcq-pvw2-xh7v fix. The helper only
existed to seed the exec() that validate_code no longer performs, so its
only remaining references were its own isolation tests. Remove both, and
assert function compile errors are empty in the non-execution regression
test.

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2026-06-18 13:26:43 -07:00
094694d3f2 fix(security): derive Fernet key from short secrets with SHA-256 (GHSA-jxw3-mjmx-3pqm) (#13704)
* fix(security): derive Fernet key from short secrets with SHA-256 (GHSA-jxw3-mjmx-3pqm)

GHSA-jxw3-mjmx-3pqm: ensure_fernet_key() seeded Python's non-cryptographic
random module with the SECRET_KEY to derive the 32-byte Fernet key when the
key was shorter than 32 chars, making the encryption key predictable and
mutating global PRNG state. Derive it deterministically with SHA-256 instead.

Deployments with a SECRET_KEY shorter than 32 chars derive a different key
after this change and must re-enter encrypted secrets; the default 43-char
secrets.token_urlsafe(32) SECRET_KEY is unaffected.

* [autofix.ci] apply automated fixes

* [autofix.ci] apply automated fixes (attempt 2/3)

* test(security): point Fernet key regression test at the SHA-256 guard

The PRNG-independence assertion (key_a == key_b across perturbed global
random state) held under the old impl too — it re-seeded with the secret
on every call, so it was deterministic per secret. Rename the test and
rewrite the docstring so it points at the assertion that actually catches
the regression: the SHA-256 + base64url equality, which the old
random.getrandbits path could never produce.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-18 13:25:44 -07:00
504f760989 fix(security): block code injection via the Tweaks API (CWE-94) (#13705)
* fix(security): block code injection via the Tweaks API (CWE-94)

apply_tweaks() only refused to override the field literally named "code", but
code-execution components expose their executable input under other names
(python_code, script, ...). An authenticated user could inject arbitrary Python
through POST /api/v1/run/{flow_id} tweaks (e.g. setting python_code on a Python
Interpreter node), achieving RCE.

Block tweaks by field type ("code") and by code-execution component type
(CODE_EXECUTION_COMPONENT_TYPES) instead of a single field-name blacklist.
Applied to both the langflow and lfx copies of apply_tweaks.

* [autofix.ci] apply automated fixes

* [autofix.ci] apply automated fixes (attempt 2/3)

* fix(security): scope Tweaks code-field block to code/sandbox inputs

Addresses review on PR #13705. The blanket "block every field on a
code-execution component" guard over-blocked benign inputs (name,
description, data, sample_size) and mislabeled them as code in the log.

Scope the block to the actual code-bearing fields: a tweak is refused when
the field is type "code", literally named "code", or is a code/sandbox input
of a code-execution component. The executable inputs (python_code, tool_code,
filter_instruction) serialize as plain "str", so the field-type=="code" guard
alone would re-open the CWE-94 bypass — they are blocked by name via the new
CODE_EXECUTION_FIELD_NAMES set, kept beside CODE_EXECUTION_COMPONENT_TYPES so
the two consumers stay in sync. global_imports stays blocked (it is the
import allow-list feeding the exec() namespace / documented sandbox boundary).

Also: name the offending field in the warning, drop the always-true
`if tweak_name in template_data` and de-indent, and add the missing lfx
regression suite (src/lfx/tests/unit/test_process.py) so the two apply_tweaks
copies cannot drift.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* test(security): tripwire so code-exec types must register their code fields

Addresses PR review: the sync between CODE_EXECUTION_COMPONENT_TYPES and
CODE_EXECUTION_FIELD_NAMES was comment-only, so adding a fifth code-execution
component and registering its type without its code field would silently drop
the by-name half of the Tweaks guard for that component.

The component classes aren't importable in the lfx unit env (optional deps),
so introspection isn't viable. Instead add a self-contained checksum test that
forces the next person to keep the two sets in lockstep:
- test_every_code_execution_type_has_registered_code_fields: every type in
  CODE_EXECUTION_COMPONENT_TYPES must have a registry entry, and each entry's
  fields must be covered by CODE_EXECUTION_FIELD_NAMES (or the globally-blocked
  "code"). Adding a type without its code field fails the suite.
- test_no_unclaimed_code_execution_field_names: catches stale frozenset entries
  left behind after a component is removed/renamed.

Point the flow_validation.py comment at the new guard test by name.

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-18 13:19:23 -07:00
cf86bf6d83 fix(security): require authentication for POST /api/v2/registration/ (CWE-306) (#13707)
* fix(security): require authentication for POST /api/v2/registration/ (CWE-306)

The registration POST endpoint declared no authentication dependency, so any
anonymous caller could overwrite the instance's registered email and trigger
outbound email-telemetry calls (using the server as a relay). The GET endpoint
already required authentication; the POST was inconsistently unprotected.

Add Depends(get_current_active_user) to the POST endpoint, matching the GET.

* [autofix.ci] apply automated fixes

* [autofix.ci] apply automated fixes (attempt 2/3)

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2026-06-18 13:17:12 -07:00
842e815be8 chore: dep update
dep update
2026-06-18 15:37:38 -04:00
d68e4bbe08 fix: route IBM WatsonX selections to model_id endpoint in unified get_llm (#13671) (#13684)
* fix: route IBM WatsonX selections to model_id endpoint in unified get_llm

A model selection sourced from the GET /api/v1/models catalog (which the
frontend uses to augment the Language Model / Agent dropdown right after a
provider is configured) carries only raw create_model_metadata fields — none
of the enriched *_param keys that get_language_model_options injects. get_llm
already derived model_class from the provider mapping for this case but still
let model_name_param / api_key_param / url_param / project_id_param fall back to
the generic defaults.

For IBM WatsonX this is load-bearing: langchain_ibm.ChatWatsonx exposes both a
`model` field (routes to the OpenAI-style Model Gateway, a different catalog)
and a `model_id` field (routes to ModelInference, the foundation-models
endpoint the dropdown is populated from). Falling back to the generic `model`
sent the selected foundation-model id to the gateway, failing with
"model <id> not found" / IAM "Provided user not found or active" — even though
the dropdown, connection test, and standalone IBM watsonx.ai component all work.

Derive all param names from get_provider_param_mapping(provider) when the
selection metadata lacks them. Only WatsonX (model_id/apikey) differs from the
generic defaults, so OpenAI/Anthropic/etc. are unaffected.

Fixes #13671

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* [autofix.ci] apply automated fixes

* [autofix.ci] apply automated fixes (attempt 2/3)

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2026-06-18 14:50:35 +00:00
b796f55c9c fix(load): bound remote flow fetches (#13660)
* fix(load): bound remote flow fetches

* test(load): cover get_flow timeout path and cap error body size

Builds on the bounded-timeout fix for #13653:
- add a direct test for the httpx.TimeoutException -> UploadError mapping
  (the core hang scenario; previously only the timeout *config* and HTTP
  error path were covered)
- truncate large upstream error bodies (GET_FLOW_ERROR_BODY_LIMIT) so an
  HTML 500 page can't bloat the UploadError message/logs
- assert against GET_FLOW_TIMEOUT instead of a magic 30.0

---------

Co-authored-by: Lucas Ma <7184042+pony-maggie@users.noreply.github.com>
Co-authored-by: Eric Hare <ericrhare@gmail.com>
2026-06-18 00:52:21 +00:00
dd37e18e22 fix(security): gate public user self-registration (CWE-862) (#13706)
* fix(security): gate public user self-registration (CWE-862)

POST /api/v1/users/ declared no authentication dependency, so any anonymous
caller could create accounts on any instance (unrestricted account creation;
immediately usable when NEW_USER_IS_ACTIVE=true). Privilege escalation is not
possible (UserCreate excludes is_superuser), but open registration is.

Add an ENABLE_SIGNUP auth setting (default True) and refuse registration when
AUTO_LOGIN is enabled (single-user mode has no signup concept) or ENABLE_SIGNUP
is False. Multi-user instances keep working sign up; operators can disable it
explicitly.

* [autofix.ci] apply automated fixes

* [autofix.ci] apply automated fixes (attempt 2/3)

* Update test_users.py

* Update .secrets.baseline

* fix(security): allow authenticated superusers to create users when public signup is disabled

The public signup gate on POST /api/v1/users/ ran before any identity check, so it rejected authenticated superusers exactly like anonymous callers. Because the condition also includes AUTO_LOGIN, it fired on the default local config too, breaking the admin "add user" UI (AdminPage -> useAddUser -> POST /api/v1/users/) on both multi-user (ENABLE_SIGNUP=False) and default (AUTO_LOGIN) deployments.

Resolve the optional current user via get_current_user_optional and skip the gate for an authenticated active superuser, so disabling public signup only blocks the anonymous path. get_current_user_optional short-circuits to None for credential-less requests, so the anonymous path can never be promoted to a superuser (even under skip_auth_auto_login), while still honoring the access_token_lf cookie the browser admin UI relies on.

Add tests pinning superuser-success and non-superuser/anonymous-refusal when signup is disabled, and fold the ENABLE_SIGNUP docstring into the Field description to match ENABLE_SUPERUSER_CLI.

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2026-06-17 21:42:10 +00:00
a8deb35ca0 fix(security): bump residual deprecated transitive uuid to v11 (#13703)
The scanner-flagged uuid 10.0.0 was already resolved on this branch by the
direct bump to uuid ^14. The only remaining old uuid was a dev-only transitive
uuid 8.3.2 pulled by jest-junit (deprecated "uuid@10 and below"). Add a scoped
npm override forcing jest-junit's uuid to ^11 (the CJS-compatible version
upstream recommends), clearing the deprecated transitive from the lockfile.

Dev-dependency / test-reporter only; no runtime impact.
2026-06-17 09:22:55 -07:00
df0cedfe25 fix(security): block code-bearing nodes with empty type in flow validation (GHSA-mfp9-86w4-493f) (#13697)
* fix(security): block code-bearing nodes with empty type in flow validation (GHSA-mfp9-86w4-493f)

_get_invalid_components() did "if not component_type: continue", so a flow node
with an empty/missing data.type but a populated template.code.value was skipped
by the custom-component gate - yet instantiate_class still executed the node's
stored code at build time (it keys off the code, not the type). An authenticated
user could therefore bypass allow_custom_components=false and run arbitrary
Python.

Now a node carrying code with an unresolvable/empty type is classified as
blocked (it can never match a trusted code hash) instead of being skipped.

* [autofix.ci] apply automated fixes

* [autofix.ci] apply automated fixes (attempt 2/3)

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2026-06-17 09:17:36 -07:00
88ed381cf0 fix: catch RecursionError when converting tools to SPARC specs on Python 3.14
`PreToolValidationWrapper.convert_langchain_tools_to_sparc_tool_specs_format`
degrades to a minimal tool spec when a tool can't be introspected, but its
`except` tuple omitted `RuntimeError`. On Python 3.14 + langchain-core 1.4.7,
reading `tool.args` for a tool whose `args_schema` is not a usable model (e.g. a
`@property` that shadows the inherited Pydantic field) returns the raw property
object, so langchain's `get_all_basemodel_annotations` recurses on
`get_origin(<property>) -> None -> None -> ...` and raises `RecursionError` (a
`RuntimeError` subclass) -- which escaped the handler and crashed conversion
(surfaced by test_altk_agent_tool_conversion.py::test_error_handling).

Add `RuntimeError` to the caught tuple, matching the two sibling handlers in the
same module that already catch it. This restores the intended fall-back to a
minimal spec and hardens real agent runs against the same recursion.
2026-06-16 19:57:10 -07:00
a383f4d426 fix: guard OTel FastAPI span route extraction against 0.137 lazy includes
FastAPI 0.137 made `include_router` lazy: included sub-routers are stored as
`_IncludedRouter` wrappers (no `.path`) in `app.routes` instead of being
flattened. `opentelemetry-instrumentation-fastapi` names spans by walking
`app.routes` and reading `route.path`; its `Match.FULL` branch already guards a
missing `.path`, but the `Match.PARTIAL` branch does not. A partial match -- e.g.
an OPTIONS/CORS preflight against a GET-only route -- raised
`AttributeError: '_IncludedRouter' object has no attribute 'path'` mid-request and
turned into a 500 (surfaced by test_security_cors.py::TestCORSIntegration).

`instrument_app` hard-codes the span-detail callback and exposes no override hook,
so patch the module-level `_get_route_details` (resolved as a global on every call)
with a guarded copy that falls back to the include prefix, then the request path,
when the matched route has no `.path`. Applied in `create_app` before
`FastAPIInstrumentor.instrument_app`; idempotent and a no-op on FastAPI <=0.136.

Follow-on to #13710 (same lazy-include root cause, different consumer).
2026-06-16 18:39:41 -07:00