* 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'.
* 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.
* 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>
* 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>
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>
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>
* 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>
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.
* 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>
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>
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>
* 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>
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
* 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>
* 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>
* 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>
* 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>
* 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>
* 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>
* 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>
* 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>
* 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>
* 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
* 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>
* 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>
* 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>
* 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>
* 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>
* 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>
* 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>
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.
* 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>
`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.
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).
The recent uv update bumped FastAPI 0.136.3 -> 0.137.1 (Starlette 1.2.1 ->
1.3.1), which changed `include_router` to lazy inclusion: an included sub-router
is now stored as an internal `_IncludedRouter` wrapper instead of eagerly
flattening its `APIRoute` objects onto the parent. Anything that introspects
`app.router.routes` no longer sees nested routes.
Production impact: `_get_route_keys` (plugin conflict-protection) iterates
`app.router.routes` to build the reserved set before loading plugins. With the
core routers now behind `_IncludedRouter` wrappers, the reserved set was empty,
so a plugin could silently shadow core Langflow routes. Fix by descending
through the wrapper via its public `original_router` / `include_context`
(accumulating the include-time prefix), so discovery works on both eager
(<=0.136) and lazy (>=0.137) inclusion.
The same assumption broke route-introspection tests, now made version-agnostic:
- test_deployments_routes.py: read routes from the router via a flattener
- test_extensions_route_guard.py: `collect_paths` descends through the wrapper
These failures surfaced on release-1.10.1 and are unrelated to any PR content.