* feat(lfx): verified-JWT identity forwarding for lfx serve
Add an optional per-user identity layer to `lfx serve` so the edge gateway's
authenticated caller is threaded into flow execution as `user_id`. Identity
must arrive as a cryptographically verifiable JWT by default; plain gateway
headers are accepted only under an explicit, weaker trust mode.
- serve_identity.py: IdentityConfig (off / jwt / header modes, env round-trip)
and IdentityVerifier (pyjwt) with algorithms pinned to RS256/ES256 (rejects
alg:none and HMAC alg-confusion before any crypto), exact iss, aud
containment, exp/nbf with skew, and a per-process TTL'd JWKS cache with
startup prefetch, rate-limited refresh, and keep-cache-on-fetch-failure. The
raw token never reaches a log line or error body.
- Wiring: /flows/{id}/run and /stream gain an identity dependency that
sub-depends on verify_api_key, so identity only annotates requests that
already cleared the serve-key floor. execute_graph_with_capture threads the
resolved user_id through run + stream paths and graph.user_id.
- CLI options propagated to uvicorn workers via LFX_SERVE_IDENTITY_* (each
worker keeps its own JWKS cache).
- Startup notices (failed JWKS prefetch, header-mode trust warning) are emitted
on a guaranteed stderr console as well as the logger, since the structlog
logger is not reliably surfaced on the serve stdout path under uvicorn.
Tests use a locally generated RSA keypair and an in-memory fake JWKS (no
network) covering off-mode regression, identity threading, the reject matrix,
rate-limited refresh, warm-cache resilience, prefetch recovery, header mode,
the serve-key floor, multi-worker env round-trip, and token-never-logged.
* [autofix.ci] apply automated fixes
* fix(lfx): require HTTPS for JWKS/issuer and fail closed on malformed JWKS
Security: plaintext http:// JWKS/issuer URLs are MITM-able — an attacker who
tampers with the response serves their own JWKS and forges tokens with the
expected iss/aud. Require https:// by default for the JWKS URL and the
issuer-derived OIDC discovery URL (and the discovered jwks_uri), with an
explicit --identity-jwt-allow-insecure-http / LFX_SERVE_IDENTITY_ALLOW_INSECURE_HTTP
escape hatch for local/dev. Non-http(s) schemes remain refused.
Availability: malformed external JSON (a JWKS or OIDC discovery doc that is a
JSON array rather than an object, or a non-string jwks_uri) raised an uncaught
AttributeError out of prefetch/request handling — aborting startup or 500ing a
request instead of failing closed. Guard the shapes and raise IdentityConfigError
so they funnel into the existing fail-closed (401) path.
Also adds ES256 unit coverage (the second accepted algorithm was only exercised
manually before).
Tests: +14 (HTTPS policy + escape hatch, malformed-shape fail-closed, ES256).
* fix(lfx): resolve identity OptionInfo leak and ANSI-fragile help assertion
serve_command declared the identity params as typer.Option(...). Direct
(non-CLI) callers that omit them got the raw OptionInfo as the value, so
IdentityConfig(mode=<OptionInfo>) raised on every serve. Convert them to
plain defaults (the CLI options live on serve_command_wrapper), completing
the pattern already applied to identity_jwt_allow_insecure_http.
Also strip ANSI before the serve --help substring assertion: rich colorizes
each hyphen-segment of an option name, so '--identity-mode' is never a literal
substring when color is forced (as in CI).
Additionally reject whitespace-only identity claims, not just empty/None, and
cover it with a parametrized test.
* Fix jwks fetch to use httpx client, avoiding redirects
---------
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
* feat(auth): external trusted JWT auth + JIT user mapping
Adds the OSS half of trusted external identity support:
- New EXTERNAL_AUTH_* AuthSettings (off by default) covering provider
key, token transport (header/cookie), JWKS or trusted-decode, claim
mapping, and a pluggable EXTERNAL_AUTH_IDENTITY_RESOLVER import path.
- New services/auth/external.py with JWT/JWKS validation, identity
resolver protocol, and token extraction helpers.
- AuthService.get_or_create_user_from_claims +
extract_user_info_from_claims implement the existing BaseAuthService
JIT hook through SSOUserProfile - no new tables.
- _authenticate_with_token falls back to external resolution when the
native JWT path fails, so Authorization-header callers transparently
upgrade to external auth.
- Token extractors in services/auth/utils.py consult the configured
external header/cookie after the native JWT path on session,
WebSocket, SSE, and optional-user dependencies.
- /api/v1/session catches AuthenticationError so external-credential
failures resolve to authenticated=False rather than 500.
Tests: 14 unit tests for external.py (JWT decode, claim mapping,
custom resolver) plus 3 integration tests in test_login.py exercising
the session endpoint JIT path (header + cookie + expired-token).
Co-Authored-By: phact <estevezsebastian@gmail.com>
Co-Authored-By: Lucas Oliveira <62335616+lucaseduoli@users.noreply.github.com>
Based-On: langflow-ai/langflow#13280
* Update src/backend/base/langflow/services/auth/external.py
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
* fix(auth): refetch JWKS once on unknown kid to survive IdP key rotation
Without this, a token signed by a newly rotated IdP key is rejected for
up to JWKS_CACHE_TTL_SECONDS (5 min) because the cached JWKS predates
the key. On a kid miss we now refetch the JWKS once, rate-limited to
one forced refresh per 30s per URL so attacker-supplied kids cannot
hammer the IdP's JWKS endpoint.
Adds JWKS-path tests (signature verification, rotation refetch,
rate-limited refresh) that were previously uncovered.
* feat(auth): add external access ceiling for trusted auth (#13637)
* feat(auth): add external access ceiling
* fix(auth): tolerate minimal external access settings
* fix(auth): address external-auth review (require JWKS audience, untangle authz/auth)
Resolves Gabriel's PR review feedback on #13293:
- Require EXTERNAL_AUTH_AUDIENCE on the JWKS verification path. Previously a
JWKS-only config verified signature + exp but left aud/iss unbound, so a token
the same IdP minted for a *different* relying party was accepted. decode now
fails closed (before any network fetch) with an actionable message and binds
aud; iss stays verified-when-set. Adds wrong-aud / missing-aud tests.
- Move the request-scoped action ceiling out of auth/external.py into a new
authorization/access_ceiling.py so the authorization package no longer imports
the auth layer (the only such import). Guards consult the authz-owned
primitive; the auth layer only derives the ceiling and installs it. external.py
re-exports the names for callers that derive/inspect the ceiling.
- _unique_external_username reuses _external_username_fallback instead of
re-implementing the provider-digest formula.
- Read the non-optional EXTERNAL_AUTH_* settings as plain attributes (drop
getattr(..., default), which re-hardcoded Field defaults) in service.py and
api_key/crud.py.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* feat(authz): pass API key context to authorization (#13639)
* chore: bump version to 1.10.1
* fix(lfx): restamp component index version after 1.10.1 fork bump (#13575)
Companion to the release-1.11.0 fix (#13574). The 1.10.1 fork bump left component_index.json's version at 1.10.0; _read_component_index fails closed on the exact-version mismatch, so the bundled registry loads as None and the upgrade-gate LFX tests fail on this branch's CI.
Surgical restamp: version -> 1.10.1, sha256 recomputed with the build script's exact hashing/serialization. Entries unchanged; 2-line diff. The three affected tests pass locally.
* fix(ci): rebalance backend test splits with measured durations + raise unit-test step timeout (1.10.1) (#13585)
fix(ci): rebalance backend test splits with measured durations + raise unit-test step timeout
Unit Tests - Python 3.12 - Group 3 has been failing at 98-99% on the
nightly: the job hits the nick-fields/retry 40-minute per-attempt timeout
and pytest is SIGTERM'd mid-test, so it looks like one flaky last test
when it is actually a deterministic timeout (and the internal retry plus
run-level retries can never succeed).
Root cause is twofold:
1. .test_durations was last regenerated ~May 2025 and covered only 2,219
of ~9,500 current unit tests, so pytest-split weighted 77% of the
suite at the 0.73s average. The expensive client-fixture tests
(test_webhook.py, test_login.py - each pays a full create_app +
lifespan boot per test, 60-120s late in a CI run) clustered into
group 3's tail, making it ~8-10 minutes slower than its siblings
(33:15 on 3.13, >40min on 3.12 which is additionally slowed by
astrapy disabling SSL connection reuse on Python 3.12.0-11).
The weekly store_pytest_durations workflow that should refresh the
file has been dying at the 6-hour GitHub job limit every week (serial
full-suite run no longer fits), so the file silently froze.
This regenerates the file from real per-test wall-clock measured in
the passing Python 3.13 nightly jobs (run 27246228762 attempt 1, all
5 groups, parsed from the -vv xdist logs: per-worker start-to-start
deltas). 9,508 tests now have measured durations; old entries are
kept where no new measurement exists. Simulated least_duration
split goes from one outlier group to 5 even groups (~24.6 min each)
with the >50s tests spread 1-2 per group instead of 7 in one.
2. timeout_minutes 40 -> 50 gives headroom for matrix-cell variance
(3.12 runs ~20% slower than 3.13) so a slightly slow cell degrades
gracefully instead of burning 2x40min and failing the whole run.
Follow-ups (not in this PR): fix store_pytest_durations to run with
xdist or split groups so it fits the 6h limit; investigate the in-worker
degradation that makes client-fixture boots cost 8-12s early in a run
but 60-120s after ~30 minutes.
* fix(test): raise spawn-child join timeout in test_multi_process_visibility (1.10.1) (#13590)
fix(test): raise spawn-child join timeout in test_multi_process_visibility
The test spawns a child via multiprocessing spawn context, which
cold-imports the full langflow package (plus coverage's multiprocessing
hooks in CI) before appending a single event. The 10s join timeout is
routinely exceeded on a loaded CI runner sharing 4 vCPUs with a second
xdist worker: in nightly run 27253229568 (Unit Tests - Python 3.12 -
Group 5) the test failed all 12 executions (5 reruns x 2 step attempts),
each rerun exactly 10s apart - the join deadline, not a product bug.
Raise the liveness bound to 60s (join returns immediately when the
child exits, so the passing case is unaffected) and kill the child on
timeout so a hung spawn can't leak into later tests.
* fix(ci): anchor langflow-base version extraction in nightly docker build (1.10.1) (#13593)
fix(ci): anchor langflow-base version extraction in nightly docker build
The first nightly on the 1.11 workspace (tag v1.11.0.dev0, run
27253229568) failed in Build Nightly Base Package within seconds:
'Base version format is incorrect'. The extraction (uv tree, grep
langflow-base unanchored, awk field 3, first line) broke because uv
tree now prints the langflow-base workspace-root line
('langflow-base v1.11.0.dev0', two fields) before any dependency line
('langflow-base[complete] v1.11.0.dev0' under the langflow root, three
fields), so the first match yields an empty field 3 and the format
check exits 1. uv tree's stderr is discarded, which made the real
cause invisible in CI.
Anchor to the root line and take field 2 instead - the exact pattern
the langflow (main package) extraction in this same file already uses,
which is why the main-package builds passed on the same tag. Verified
both extractions print 1.11.0.dev0 against a local checkout of
v1.11.0.dev0.
* fix(test): gate models.dev background refresh out of tests (1.10.1) (#13598)
fix(test): gate models.dev background refresh out of tests
Integration tests failed twice in nightly run 27260425158 with pyleak
EventLoopBlockError - first Integration Tests 3.14, then 3.12 on the
rerun, each time in a different test. The blocking stack points at
refresh_models_dev_periodically: every app boot unconditionally starts
a lifespan task that immediately fetches https://models.dev/api.json,
so the request lands mid-test in whatever test happens to be running.
Under pyleak's asyncio debug instrumentation the fetch blocked the loop
0.797s against a 0.2s threshold. Whichever test draws the short straw
flakes - which is why it looked transient and moved between versions.
Add a LANGFLOW_MODELS_DEV_REFRESH env gate (default unchanged: enabled)
and disable it session-wide in the backend test conftest. Tests fall
back to the bundled static model lists, which is also deterministic.
Verified: with the gate set, app boot makes zero models.dev requests;
the previously failing integration test passes.
* fix(ci): allow pre-releases when pinning the nightly in migration validation (1.10.1) (#13601)
fix(ci): allow pre-releases when pinning the nightly in migration validation
Migration Test: pip/venv (stable -> nightly) failed deterministically on
nightly run 27260425158 (twice, including a rerun):
hint: langflow-base was requested with a pre-release marker (e.g.,
langflow-base==1.11.0.dev1), but pre-releases weren't enabled
(try: --prerelease=allow)
This is the first nightly publishing as a canonical .devN pre-release
of the langflow distribution (nightly -> stable bundle cutover). The
'latest' branch of the upgrade step already passes --prerelease=allow,
but the pinned-version branches do not. uv implicitly allows the
pre-release for the directly requested ==X.Y.Z.devN pin, yet langflow's
metadata pins langflow-base==X.Y.Z.devN transitively, and uv rejects
transitive pre-releases unless they are enabled - so the install fails
after the stable uninstall, sinking the migration test.
Add --prerelease=allow to both pinned-version install lines.
* fix(ci): scope nightly migration-test pre-releases to the langflow stack (1.10.1) (#13605)
fix(ci): scope nightly migration-test pre-releases to the langflow stack
The previous fix (#13599) added a global --prerelease=allow, which let
UNRELATED dependencies resolve to alphas: on nightly run 27274206250
the stable->nightly upgrade pulled pydantic 2.14.0a1 + pydantic-yaml
1.6.1a1, and the pydantic alpha breaks langchain-core at import time
(RunnablePassthrough pydantic ValidationError), failing the nightly
boot right after a successful install. Clean installs were fine - only
this upgrade path resolved the alpha combo.
Scope pre-release eligibility to the langflow lockstep stack instead:
uv accepts a pre-release when the package's own requirement carries a
pre-release marker, but not via transitive pins, and the nightly chain
is langflow -> langflow-base -> lfx -> langflow-sdk with exact ==devN
pins. Request each directly; langflow-sdk versions independently
(0.2.0.devN), so an explicit .dev0 floor marks it eligible while lfx's
exact pin selects the version. The 'latest' branch gets the same
treatment via .dev0 floors on all four.
Verified by dry-run against PyPI: langflow/langflow-base/lfx at
1.11.0.dev2 + langflow-sdk 0.2.0.dev2 resolve with pydantic staying at
stable 2.13.4.
* fix(ci): create GitHub releases on the dispatched v-prefixed tag (1.10.1) (#13609)
fix(ci): create GitHub releases on the dispatched v-prefixed tag
The create_release job passed the bare version (v stripped) as the
release tag with no commit target, so when that tag did not exist
GitHub minted a new lightweight tag at the default-branch HEAD -- the
wrong commit, still carrying the previous version (main only adopts a
release's version via the post-release back-merge). Every release since
1.8.2 shipped a stray bare tag (1.8.2, 1.8.3, 1.9.0-1.9.6, 1.10.0)
pointing at a previous-version commit, and the GitHub release had to be
manually re-pointed to the real vX.Y.Z tag after each release.
- create_release now attaches the release to inputs.release_tag for
stable releases; pre-releases keep their computed tag (e.g.
1.10.0rc1) but it is minted at the release commit via 'commit:'.
- release-lfx.yml pins the minted lfx-v* tag to github.sha instead of
the default branch.
The validate-tag-format guard (#12847) only blocks at dispatch time;
create_release was re-creating the very duplicates it guards against.
* fix(ci): resolve validate-version output in release-lfx changelog link (1.10.1) (#13611)
fix(ci): resolve validate-version output in release-lfx changelog link
The create-release job references
needs.validate-version.outputs.current_version in its generated release
notes (the Full Changelog compare link), but validate-version was not in
the job's needs array, so the expression evaluated empty and the link
rendered as compare/v...lfx-vX.Y.Z (broken base).
Add validate-version to the create-release needs. This adds no real
serialization: create-release already waits on release-lfx, which
transitively requires validate-version via run-tests, and the job's
always() if-condition is unaffected.
Flagged by actionlint: property "validate-version" is not defined in
object type {build-docker, release-lfx}.
* feat(auth): external trusted JWT auth + JIT user mapping
Adds the OSS half of trusted external identity support:
- New EXTERNAL_AUTH_* AuthSettings (off by default) covering provider
key, token transport (header/cookie), JWKS or trusted-decode, claim
mapping, and a pluggable EXTERNAL_AUTH_IDENTITY_RESOLVER import path.
- New services/auth/external.py with JWT/JWKS validation, identity
resolver protocol, and token extraction helpers.
- AuthService.get_or_create_user_from_claims +
extract_user_info_from_claims implement the existing BaseAuthService
JIT hook through SSOUserProfile - no new tables.
- _authenticate_with_token falls back to external resolution when the
native JWT path fails, so Authorization-header callers transparently
upgrade to external auth.
- Token extractors in services/auth/utils.py consult the configured
external header/cookie after the native JWT path on session,
WebSocket, SSE, and optional-user dependencies.
- /api/v1/session catches AuthenticationError so external-credential
failures resolve to authenticated=False rather than 500.
Tests: 14 unit tests for external.py (JWT decode, claim mapping,
custom resolver) plus 3 integration tests in test_login.py exercising
the session endpoint JIT path (header + cookie + expired-token).
Co-Authored-By: phact <estevezsebastian@gmail.com>
Co-Authored-By: Lucas Oliveira <62335616+lucaseduoli@users.noreply.github.com>
Based-On: langflow-ai/langflow#13280
* Update src/backend/base/langflow/services/auth/external.py
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
* fix(auth): refetch JWKS once on unknown kid to survive IdP key rotation
Without this, a token signed by a newly rotated IdP key is rejected for
up to JWKS_CACHE_TTL_SECONDS (5 min) because the cached JWKS predates
the key. On a kid miss we now refetch the JWKS once, rate-limited to
one forced refresh per 30s per URL so attacker-supplied kids cannot
hammer the IdP's JWKS endpoint.
Adds JWKS-path tests (signature verification, rotation refetch,
rate-limited refresh) that were previously uncovered.
* fix(components): follow HTTP redirects in URL component with per-hop SSRF revalidation (#13572)
* fix(components): follow HTTP redirects in URL component with per-hop SSRF revalidation
The URL component sent every request with follow_redirects=False, so any
site whose entered URL 301s to its canonical address (http->https or
www/non-www normalization) returned the redirect stub - e.g. a bare
"301 Moved Permanently / nginx" page - as the scraped content, or failed
outright when the redirect response had an empty body.
Redirects are now followed via a new advanced "Follow Redirects" input
(default on). When SSRF protection is enabled, hops are followed manually
and each Location target is re-validated with the same blocked-IP denylist
and DNS pinning as the initial request before any connection is made,
mirroring the API Request component; cross-host hops drop sensitive
headers and chains are capped at 20 redirects.
* fix(components): compare full origin when keeping credentials across redirects, crawl from post-redirect base
Addresses CodeRabbit review:
- _headers_for_redirect now keeps Authorization/Proxy-Authorization/Cookie
only for same-origin hops (scheme, host, port) or a direct http->https
upgrade on default ports, exactly the cases where httpx keeps the
Authorization header. Applied to both the URL and API Request
components (the helper was copied from the latter).
- _crawl_recursive resolves relative links and the prevent_outside check
against the final post-redirect URL, and marks it visited, so depth>1
crawls work on sites that 301 to their canonical address.
* chore: regenerate component index and starter projects for release-1.10.1 base
The autofix.ci jobs uploaded but never pushed the regeneration after the
rebase, leaving the 4 URL-component templates with a stale field_order
(missing follow_redirects) and the index without the new code hashes.
Generated with the same commands CI uses: LFX_DEV=1 make
build_component_index + scripts/ci/update_starter_projects.py.
Pokedex Agent / Structured Data Analysis Agent pick up the API Request
component's new code_hash from the origin-comparison fix.
* [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(ci): make release Biome lint green — NUL-delimit file list + clear pre-existing lint errors (#13550)
The Lint Frontend job runs against `main` as the base. In a release
(workflow_dispatch) run it diffs the entire release branch (~915 files) and
re-lints nearly the whole frontend, exposing two issues that per-PR linting
never hits together:
1. xargs split starter-project spec paths containing spaces (e.g.
"News Aggregator.spec.ts" -> "News" + "Aggregator.spec.ts"), producing
`internalError/io: No such file or directory`. NUL-delimit the file list
so spaces are preserved. (supersedes #13381)
2. The whole-branch diff surfaced 30 pre-existing Biome errors: 22
noExplicitAny + 8 organizeImports. Resolved with real types where safe
(freezeObject generic, ColDef defaults, messagesSorter field shape,
VertexBuildTypeAPI, Record<string,string>, DragEvent<HTMLElement>,
unknown for narrowed values) and justified biome-ignore for genuinely
loose cases (polymorphic display values, test global stubs, captured
unexported StreamCallbacks). Imports auto-sorted via biome.
Verified locally: biome check on the full release-vs-main file set is now 0
errors (was 30); tsc unchanged at its 303-error baseline (no new type errors).
* fix: limit public flow endpoint from displaying private flow streams (#13602)
* fix: limit public flow endpoint from displaying private flow streams
* fix: Address coderabbit reviews
---------
Co-authored-by: Janardan S Kavia <janardanskavia@Janardans-MacBook-Pro.local>
* fix: enforce the FileSystemTool credential deny-list (#13625)
* enforce the FileSystemTool credential deny-list
* security checks gh review
* [autofix.ci] apply automated fixes
* feat(authz): pass API key context to authorization
* refactor(authz): address review feedback on API-key auth context
- Add AuthCredentialContext.from_api_key_result() and use it at all six
API-key projection sites (service.py x5, mcp_projects.py) so the caveat
fields stay in sync and no site can silently drop one.
- authz_me builds the enforce context from the public
current_auth_context_for_authz() helper instead of reaching into the
private guards._auth_context.
- Clear request-local credential context at the top of verify_project_auth
to match the service.py entrypoints, so the composer-token fast path can
never inherit stale context.
---------
Co-authored-by: phact <estevezsebastian@gmail.com>
Co-authored-by: Lucas Oliveira <62335616+lucaseduoli@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Janardan Singh Kavia <janardankavia@ibm.com>
Co-authored-by: Janardan S Kavia <janardanskavia@Janardans-MacBook-Pro.local>
Co-authored-by: Cristhian Zanforlin Lousa <cristhian.lousa@gmail.com>
* [autofix.ci] apply automated fixes
* fix(auth): close access-ceiling bypasses and harden external JWT auth
Address review findings on the external trusted-auth + access-ceiling work:
- Enforce the access ceiling on execution/mutation paths that bypassed the
ensure_*_permission chokepoint: MCP project tool-call (flow execute),
flow version snapshot/activate/delete, v1 file upload/delete, and memory
base CRUD/ingest.
- Move the external-user API-key block into the shared authenticate_api_key
chokepoint so /run, v2 workflow, OpenAI-compat, WebSocket, webhook and MCP
key auth all enforce it (was only on the JWT-fallback path).
- Require exp on both the JWKS and trusted-decode paths; reject non-https
JWKS URLs (loopback http allowed for dev).
- Normalize EXTERNAL_AUTH_PROVIDER at the config boundary so the API-key floor
cannot be silently disabled by an empty/whitespace value.
- Make a configured EXTERNAL_AUTH_ACCESS_CLAIM_MAPPING authoritative: an
unmapped claim value falls to the default level instead of self-elevating
via the built-in alias table.
- Stop nulling a stored SSOUserProfile.email when a later token omits email.
- Keep the external credential usable as a fallback when a stale/invalid
native token is present (WebSocket, SSE, optional-user paths).
- Add delete to the editor access level (deploy stays admin-only).
Adds regression tests across auth, authz guards, api-key crud, and the newly
guarded routes.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* test(mcp): give handle_call_tool flow stub the attrs ensure_flow_permission reads
The access-ceiling hardening added an ensure_flow_permission(EXECUTE) guard to
handle_call_tool that reads flow.user_id and flow.workspace_id, but the
_invoke_handle_call_tool flow stub only carried id/name/folder_id, raising
AttributeError. Set user_id to match the current user so the owner-override
path is exercised, and add workspace_id.
* fix(auth): close remaining ceiling gaps + external-cookie/filesystem/job-queue hardening
Synthesizes the external review (P1-P3) with the multi-agent re-review findings.
External auth:
- P1: regular HTTP (get_current_user) and /api/v1/session now extract the
external credential separately and pass it as a fallback, so a stale/invalid
native cookie can no longer shadow a valid external credential (previously only
WS/SSE/optional paths did this).
- P2: clear the request-local external access ceiling at every auth entrypoint
(api-key, raw access-token, webhook, ws api-key, MCP), not just
authenticate_with_credentials, so a stale ceiling can never carry into a
non-external path.
- Make a configured access-claim mapping authoritative even when it parses empty
(all-invalid entries no longer re-enable alias self-elevation).
- A blocked external user's API key no longer increments usage counters.
Access-ceiling coverage (routes that previously escaped the deny-only cap):
- custom_component / custom_component_update (code instantiation) now enforce the
ceiling directly (viewer denied; editor/admin/native users unchanged).
- Deprecated /upload/{flow_id}, update_project_mcp_settings, and the models.py
default/enabled-model variable routes now call the appropriate guard.
- Memory-base guards resolve the base first and pass kb_id + real owner so plugin
enforce runs for non-owners and audit rows carry the kb id.
Bundled hardening:
- P2: filesystem deny-list now denies a protected credential directory requested
by basename (.ssh/.aws/.git) and as a glob entry, not just as a parent dir.
- P3: public-job marker write failures on the Redis backend now fail the build
(503) instead of returning an un-shareable job id that 404s on other workers.
Adds regression tests across auth, authz route guards, api-key crud, external
auth, filesystem deny-list, memory bases, and the redis job queue.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* [autofix.ci] apply automated fixes
---------
Co-authored-by: phact <estevezsebastian@gmail.com>
Co-authored-by: Lucas Oliveira <62335616+lucaseduoli@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
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: Janardan Singh Kavia <janardankavia@ibm.com>
Co-authored-by: Janardan S Kavia <janardanskavia@Janardans-MacBook-Pro.local>
Co-authored-by: Cristhian Zanforlin Lousa <cristhian.lousa@gmail.com>
* Upgrade Firecrawl components to firecrawl-py v2 + add Search component
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Upgrade Firecrawl components to firecrawl-py v2 + add Search component
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Upgrade Firecrawl components to firecrawl-py v2 + add Search component
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Upgrade Firecrawl components to firecrawl-py v2 + add Search component
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Upgrade Firecrawl components to firecrawl-py v2 + add Search component
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Upgrade Firecrawl components to firecrawl-py v2 + add Search component
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* Upgrade Firecrawl components to firecrawl-py v2 + add Search component
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* docs: add Firecrawl Search API to the bundle page
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* [autofix.ci] apply automated fixes
* fix(firecrawl): D205 docstring + defensive .get('data') in crawl
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(firecrawl): D205 docstring in scrape
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* perf(firecrawl): use list.extend in map (PERF401)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* test: add unit tests for Firecrawl v2 components
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* [autofix.ci] apply automated fixes
* test: align Firecrawl component tests with lfx _attributes pattern
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* [autofix.ci] apply automated fixes
* refactor(firecrawl): remove deprecated extract endpoint component
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* refactor(firecrawl): remove deprecated extract endpoint component
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* test: drop extract component test (extract endpoint removed)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* docs: remove Firecrawl Extract API section (deprecated)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* [autofix.ci] apply automated fixes
* fix(firecrawl): map Search component to Firecrawl icon, drop Extract
The frontend icon map in styleUtils.ts still referenced the removed
FirecrawlExtractApi and lacked the new FirecrawlSearchApi, so the Search
node would not render the Firecrawl logo.
* chore: regenerate component index for Firecrawl v2 (Search added, Extract removed)
* refactor(firecrawl): extract components into the lfx-firecrawl extension bundle
Port the firecrawl provider out of lfx.components into a standalone
Extension Bundle at src/bundles/firecrawl (distribution: lfx-firecrawl),
following src/bundles/PORTING.md:
- Move the v2-SDK components (Scrape, Crawl, Map, Search) to
src/bundles/firecrawl/src/lfx_firecrawl/components/firecrawl/ and
remove the in-tree provider + its lfx.components registration.
- Own the firecrawl-py>=4,<5 pin in the bundle; drop it from
langflow-base.
- Wire the workspace (root pyproject deps/sources/members) and uv.lock.
- Append migration-table entries (bare name, both import paths, pre-a
slot) for the four classes; FirecrawlExtractApi gets no entries since
the v2 SDK removed the extract endpoint and the component was dropped.
- Regenerate the component index (firecrawl category removed) and
locales/en.json (54 firecrawl keys move out of core).
- Bundle-local unit tests + test_pilot_firecrawl_upgrade integration
test; update Dockerfile bundle enumeration comments.
* fix(lfx): repair migration table entry fused during release-1.11.0 merge
The release-1.11.0 back-merge collided the FirecrawlSearchApi legacy_slot
entry with the NextPlaidVectorStoreComponent bare_class_name entry, mashing
both into a single object with duplicate 'target' keys, populating two of
{bare_class_name, import_path, legacy_slot}. That fails the MigrationTable
validator (entries.51) and broke the lfx loader tests.
Split it into the two intended entries so each populates exactly one
key-field. Restores the FirecrawlSearchApi and NextPlaidVectorStoreComponent
quads (60 entries total, +16 vs base). Append-only and bare-name guards pass.
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: Eric Hare <ericrhare@gmail.com>
* fix(authz): schedule periodic audit-log retention cleanup
clean_authz_audit_log() was only invoked once, at startup inside
initialize_services(). A long-running instance never pruned authz_audit_log
again after boot, so the table grew unbounded between restarts even though the
retention helper was already implemented and unit-tested.
Wire the same helper to a recurring background worker:
- New AuditLogCleanupWorker (services/task/audit_cleanup.py), modelled on the
sibling temp_flow_cleanup.CleanupWorker: a stop-event-driven asyncio task that
prunes on a fixed interval in its own session_scope(). Best-effort -- the
helper logs-and-swallows DB errors and an outer guard keeps the loop alive, so
it never blocks the event loop or request path.
- Gated: the worker only schedules when AUTHZ_AUDIT_ENABLED is True and
AUTHZ_AUDIT_RETENTION_DAYS > 0; otherwise start() is a no-op. Retention=0
stays a no-op end to end.
- Sleep-first loop: the unconditional startup sweep still prunes at boot, so the
first scheduled pass waits one interval to avoid a redundant immediate delete.
The startup sweep is left unchanged.
- New AUTHZ_AUDIT_CLEANUP_INTERVAL setting (default 86400 = daily, floor 300s).
- Started/stopped from the application lifespan, both best-effort.
Tests (test_audit_cleanup_worker.py) show cleanup runs repeatedly on the
recurring schedule (not just at startup), is a no-op when retention or auditing
is disabled, survives sweep failures, and -- end to end against a real SQLite
engine -- prunes a row inserted after startup while leaving in-window rows.
Closes the audit-retention-scheduling gap (C) for OSS RBAC on release-1.10.0.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* refactor(authz): read AUTHZ_AUDIT_CLEANUP_INTERVAL directly
The cleanup worker resolved the sweep interval via getattr-with-default
plus a TypeError/ValueError guard, mirroring the field default in a
module constant. Pydantic already guarantees the field is present and
>= 300, so those fallbacks were unreachable and the constant was a
second source of truth that could drift.
Read the setting directly; keep DEFAULT_CLEANUP_INTERVAL_SECONDS only as
the pre-start() placeholder for unstarted workers. Drop the matching
missing/garbage-fallback test assertion.
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* test(authz): RBAC enforcement + share-lifecycle integration tests via a test-double enforcer
No automated test exercised end-to-end authorization enforcement. The OSS
authorization service is a pass-through (enforce() always returns True,
supports_cross_user_fetch() is False), so allow/deny semantics could not be
asserted against it — only guard-wiring unit tests existed.
Add a lightweight, OSS-only test-double enforcer plus integration tests that
drive the real flow routes over HTTP under a genuine allow/deny signal — no EE
Casbin package required.
- _policy_double.py: PolicyTestAuthorizationService derives allow/deny from the
seeded authz_role / authz_role_assignment / authz_share rows and sets
SUPPORTS_CROSS_USER_FETCH=True. install_policy_authz() swaps it onto the
service manager (str-enum keys make ServiceType interchangeable) and flips
AUTHZ_ENABLED=true / AUTHZ_SUPERUSER_BYPASS=false, restoring both on exit, so
every get_authorization_service() call site (guards, fetch, listing, helpers)
sees it for the duration of a test. Ships seed helpers (roles/assignments/
shares) so tests stay declarative.
- test_rbac_enforcement_integration.py:
* Role matrix (Phase 1.11) on flow routes, with flows owned by a separate user
so the guards' owner-override does not mask the role decision:
- viewer: read + execute (build) allowed; write/delete -> 404; create -> 403
- developer: write + create allowed; delete -> 404
- admin: full, including delete
* Share lifecycle (Phase 3.13): Alice shares a flow with Bob ->
Bob GET/PATCH/build succeed; without the share every fetch route -> 404
(UUID-privacy mask). A read-level share grants GET but 404s PATCH, proving
permission_level (not mere share presence) is enforced.
* Domain resolution: a workspace-scoped grant authorizes a flow in that
workspace but 404s a flow in another — trips if _resolve_authz_domain
regresses.
In each test a cross-user GET -> 200 (only possible with enforcement on +
share-aware fetch) is the linchpin proving the paired deny is real enforcement,
not an owner-scoped fetch miss; removing a guard flips a 404 to 200/200.
Closes the enforcement integration-test gap (D) for OSS RBAC on release-1.10.0.
Per the ticket's triage note, the reusable test-double also stands alone if the
team prefers the deny-path matrix to live in EE.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* test(authz): harden policy double per review — strict scoped grants, deterministic seeding, read-share build denial
- _assignment_covers: a scoped assignment missing domain_id no longer
widens to global coverage; only domain_type='global' is global
- assign_role: fail fast (ValueError) on non-global assignments without
a domain_id so malformed grants can't hide domain-resolution bugs
- seed_system_roles: overwrite preexisting viewer/developer/admin rows
so the asserted policy matrix is always SYSTEM_ROLE_PERMISSIONS
- read-only share test: also assert build/execute is denied (404), not
just PATCH
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* nextplaid integrate
* doc ids fix
* multivec fixes
* type check
* refactor: ship NextPlaid as the lfx-nextplaid extension bundle
Convert the in-tree NextPlaid vector store and its companion vLLM
multivector embeddings into a standalone `lfx-nextplaid` Extension Bundle,
following src/bundles/PORTING.md (cf. lfx-arxiv, lfx-ibm). The whole
feature now ships as an additive bundle with no core modifications.
- Move both components into src/bundles/nextplaid (components/nextplaid),
dropping src/lfx/.../components/nextplaid and reverting the core vllm
__init__ multivector additions.
- Bundle declares its own runtime deps (langchain-plaid, pillow) and ships
extension.json + the langflow.extensions entry point.
- Add migration_table entries mapping the legacy class names / import
paths to ext:nextplaid:*@official; wire the workspace (root pyproject,
uv.lock).
- Add the pilot upgrade integration test and bundle-local unit tests.
- Declare explicit outputs on NextPlaidVectorStoreComponent so the
extension validator can resolve its output methods.
* fix: address CodeRabbit review on NextPlaid bundle
- nextplaid: derive stable text IDs from source/page+content (not content
alone) and fingerprint raw PIL images by bytes (not batch position) so
ingests upsert instead of colliding/overwriting unrelated vectors
- vllm impl: validate the /pooling response envelope before nested indexing,
raising a clear RuntimeError on malformed text/image responses
- icon: use the isDark boolean prop contract instead of the isdark string
- test: gate the distribution check on package presence (PackageNotFoundError)
so genuine import failures surface instead of being skipped
---------
Co-authored-by: Meet <meet@dhcp-9-127-22-227.c4p-in.ibm.com>
Co-authored-by: Eric Hare <ericrhare@gmail.com>
* test: add component a11y unit tests (jest-axe)
Cover 14 components in two waves. 16 tests fail by design - each
encodes a known gap from a11y-action-plan (phases 0-3) and flips to
a regression lock once the component fix lands. 23 tests pass as
regression locks for already-correct semantics.
* ci: split a11y unit tests into separate workflow
Main jest CI excludes *.a11y.test.* so known-gap failures don't
block PRs; new a11y-unit-tests.yml runs them as informational check.
* fix(a11y): resolve component gaps flagged by a11y unit tests
Fixes all 16 failing jest-axe gap tests:
- TableHead defaults scope=col
- alert display area gets aria-live region
- app header becomes <header> landmark; bell button labeled
- icon wrapper decorative by default (aria-hidden), ariaLabel opt-in
- Input drops wrapping <label> (name pollution) and hides placeholder span
- password toggle back in tab order with aria-label/aria-pressed
- CheckBoxDiv exposes checkbox role + state
- accordion trigger renders native Radix button
- dialogs focus container on open instead of suppressing autofocus
- full-screen modal exposes dialog role/aria-modal/ariaLabel
- flow list card focusable with keyboard activation
Also wraps test focus calls in act() and mocks the icon loader so
the a11y suite runs without React warnings.
* fix: skip Puppeteer Chrome download in remaining Dockerfiles
frontend and base images still ran bare npm install; puppeteer (via
accessibility-checker, test-only) tried to fetch Chrome and broke the
Docker image build in CI.
* test: extend a11y unit test coverage (jest-axe)
Adds axe + semantics tests for untested primitives (button, alert,
textarea, radio-group, dropdown-menu, tooltip, popover) and two
known-gap tests that fail by design until fixes land:
- copyFieldAreaComponent copy action is a bare div onClick (plan 2.4)
- DialogContentWithouFixed renders an empty fragment (plan 1.6)
* fix: make webhook copy field a real button (a11y)
Copy action was a bare div onClick - no role, name, or keyboard
support (a11y-action-plan 2.4). Drops the no-longer-needed
DialogContentWithouFixed known-gap test.
* feat: update a11y unit tests and improve accessibility semantics in components
* feat: enable pull request trigger for a11y scan workflow
* test: add a11y regression scan suite
* test: make a11y scans non-blocking
* test: discover a11y scan specs
* ci: group a11y workflow checks
* test: add component a11y unit tests (jest-axe)
Cover 14 components in two waves. 16 tests fail by design - each
encodes a known gap from a11y-action-plan (phases 0-3) and flips to
a regression lock once the component fix lands. 23 tests pass as
regression locks for already-correct semantics.
* ci: split a11y unit tests into separate workflow
Main jest CI excludes *.a11y.test.* so known-gap failures don't
block PRs; new a11y-unit-tests.yml runs them as informational check.
* fix(a11y): resolve component gaps flagged by a11y unit tests
Fixes all 16 failing jest-axe gap tests:
- TableHead defaults scope=col
- alert display area gets aria-live region
- app header becomes <header> landmark; bell button labeled
- icon wrapper decorative by default (aria-hidden), ariaLabel opt-in
- Input drops wrapping <label> (name pollution) and hides placeholder span
- password toggle back in tab order with aria-label/aria-pressed
- CheckBoxDiv exposes checkbox role + state
- accordion trigger renders native Radix button
- dialogs focus container on open instead of suppressing autofocus
- full-screen modal exposes dialog role/aria-modal/ariaLabel
- flow list card focusable with keyboard activation
Also wraps test focus calls in act() and mocks the icon loader so
the a11y suite runs without React warnings.
* fix: skip Puppeteer Chrome download in remaining Dockerfiles
frontend and base images still ran bare npm install; puppeteer (via
accessibility-checker, test-only) tried to fetch Chrome and broke the
Docker image build in CI.
* test: extend a11y unit test coverage (jest-axe)
Adds axe + semantics tests for untested primitives (button, alert,
textarea, radio-group, dropdown-menu, tooltip, popover) and two
known-gap tests that fail by design until fixes land:
- copyFieldAreaComponent copy action is a bare div onClick (plan 2.4)
- DialogContentWithouFixed renders an empty fragment (plan 1.6)
* fix: make webhook copy field a real button (a11y)
Copy action was a bare div onClick - no role, name, or keyboard
support (a11y-action-plan 2.4). Drops the no-longer-needed
DialogContentWithouFixed known-gap test.
* feat: update a11y unit tests and improve accessibility semantics in components
* feat: enable pull request trigger for a11y scan workflow
fix(frontend): restore Inspection Panel access to hidden advanced fields
Fields that were both advanced=True and listed in HIDDEN_FIELDS became
unreachable from the Inspection Panel: hidden from the node body (advanced)
and removed from the panel (HIDDEN_FIELDS), leaving no edit path. This
affected every HIDDEN_FIELDS entry, originally reported for Chat Input/Output
Store Messages (should_store_message).
Keep HIDDEN_FIELDS out of the default advanced view (the #12473 declutter
intent) but stop filtering them out of edit-fields mode, so users can surface
and edit them via the visibility toggle. Also drop the stale Agent verbose
entry (removed from the component via drop = {verbose}) while keeping the
still-live format_instructions and output_schema hidden. i18n the two
remaining hardcoded panel strings.
Fixes#13595
fix(frontend): remove duplicate tooltip and raw i18n key on collapsed sidebar nav
The floating canvas control nav (shown when the sidebar is collapsed)
rendered two tooltips per icon: a native `title` attribute on
ControlButton plus the styled ShadTooltip. On top of that,
MemoizedComponents passed the raw i18n key (`item.tooltip`) as the
tooltip text instead of running it through `t()`, so the ShadTooltip
exposed strings like `sidebar.nav.bundles` while the native title showed
the bare id (`bundles`).
- CanvasControlButton: drop the native `title` (the duplicate tooltip);
keep accessibility via `aria-label`. The ShadTooltip is the single
visual tooltip. This also removes the redundant native tooltip from the
zoom/fit/lock control buttons.
- MemoizedComponents: translate the nav tooltip with `t(item.tooltip)`.
Adds an isolated CanvasControlButton regression test asserting no native
`title` and that the label is surfaced via aria-label + ShadTooltip.
* fix: fail fast when Redis job queue backend is unreachable
When LANGFLOW_JOB_QUEUE_TYPE=redis is set but Redis is not reachable,
Langflow booted normally and then raised a raw redis ConnectionError as
a 500 on the first flow execution, with no clear cause.
Probe Redis at startup (bounded retry) and abort boot with an actionable
error when it stays unreachable, mirroring the existing external-cache
connectivity check. Translate a later Redis outage on the build and
ownership paths into a clean HTTP 503 via a typed
JobQueueBackendUnavailableError instead of a raw stack trace.
* [autofix.ci] apply automated fixes
* [autofix.ci] apply automated fixes (attempt 2/3)
* chore: auto-bake note keys and regenerate backend locales/en.json [skip ci]
* [autofix.ci] apply automated fixes
* fix: address review - probe redis pre-start, redact credentials, cancel orphaned build
- is_connected() now probes with a temporary client when the service has
not started yet: the startup fail-fast in initialize_services() runs
before the per-worker start() creates the client, so it previously
rejected every redis boot, healthy or not
- connection_target redacts URL userinfo so credentials never reach
server logs or the HTTP 503 detail
- build_flow cancels the just-started build (best-effort) when owner
registration fails, instead of leaving an unreachable build running
- register_job_owner only records the local owner after the Redis write
succeeds, so a failed write cannot leave same-worker ownership checks
passing while other workers see the job as unowned
---------
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
* docs: migrate code blocks from CodeSnippet to native Prism
Replace the custom CodeSnippet component (@code-hike/lighter) with
Docusaurus's native @theme/CodeBlock across all MDX files (current and
versioned docs). Add bash to additionalLanguages and swizzle
prism-include-languages.js to add custom token highlighting for shell
commands and flags. Remove @code-hike/mdx dependency.
* docs: improve inline code styling
Darken inline code background, increase horizontal padding to 0.4em,
fix vertical alignment, and remove border in light mode.
* docs: address PR review — fix code slice regression and CSS/regex polish
- Inline RecursiveCharacterTextSplitter inputs and methods as literal
code blocks in concepts-components.mdx (current + 1.8.0 + 1.9.0),
restoring the focused slices lost when migrating from CodeSnippet
- Scope bash-plain Prism regex to unambiguous CLI subcommands only,
removing generic bash builtins (run, add, get, set, start, stop, etc.)
- Merge duplicate .theme-code-block CSS rules into a single declaration
* fix(docs): prevent horizontal scroll on API docs pages
The Redoc two-column layout (sidebar 300px + api-content 1300px)
totals 1600px, expanding .main-wrapper beyond narrower viewports
because it has overflow:visible. Clips at .main-wrapper using the
html.plugin-redoc class that Docusaurus adds on API pages only.
* fix(docs): API docs sidebar and layout fixes
- Disable Redoc built-in search (disableSearch: true)
- Pin sidebar top to navbar height (top: 60px) so it never hides behind navbar
- Remove hardcoded #111 background from dark mode sidebar
* fix(docs): render markdown correctly in API docs descriptions
The _clean_descriptions function was converting newlines to <br> tags,
mixing HTML with Markdown. CommonMark stops parsing Markdown headings
(###) inside HTML blocks, causing them to appear as literal text in Redoc.
Replace the <br> conversion with a simple strip() so descriptions remain
pure Markdown and Redoc renders headings, lists, and code blocks correctly.
* feat(docs): align API docs colors with Langflow brand
- Set primaryColor to #F471B5 (Langflow pink)
- Add HTTP method badge colors matching Langflow palette
- Set schema.linesColor and requireLabelColor to brand pink
- Set inline code color to pink, headers to #e3e3e3
- Set sidebar background to #18181b (matches frontend dark bg)
- Set rightPanel background to #0d0d0f, codeBlock to #161618
- Refactor: move color config from CSS to theme.theme where safe
- Remove dead search input CSS (search disabled via disableSearch:true)
- Consolidate duplicate .menu-content rules
* wip(docs): API docs styling — colors, components, light/dark themes
* fix(docs): fix Response samples h3 title padding and refactor HTTP method button CSS
* feat(docs): add sidebar dark background, active item styles, and right panel color adjustments
* fix(docs): add sidebar borders and remove operation divider border
* fix(docs): fix expanded response background and align dark/light theme colors
* refactor(docs): standardize selectors, comments and remove duplicate rules in redocusaurus.css
* refactor(docs): apply PR review — CSS custom properties, version pin, dom version comments, fix overflow
* fix(docs): fix Redocly badge visibility covered by sidebar background
* fix(docs): extend sidebar border-right to Redocly badge area
* refactor(docs): replace hardcoded #ffffff label color with --redoc-text-label variable
* fix(docs): lighten inline code background in API docs dark theme
Redoc's default typography.code.backgroundColor (rgba(38, 50, 56, 0.05))
is nearly invisible over the dark background. Override it with
rgba(255, 255, 255, 0.05) in dark theme only, excluding pre > code so
code sample blocks stay unaffected.
* feat(docs): align docs primary pink with API spec brand color
Use #f471b5 (API spec primaryColor) as --ifm-color-primary in dark theme
and #e44fa0 (slightly darkened for contrast on white) in light theme.
Remove dark-theme pink overrides in sidebar.css (#ff6ad0 CTA and
hsla(329, 55%, 68%) active TOC link) — they compensated for the old
muted pink and are redundant now that the primary itself is bright.
* feat(docs): lighten dark theme text colors for better readability
Bump body text (#a8a8b0 -> #bcbcc4), headings (#cdcdd4 -> #dcdce2)
and sidebar menu (#8a8a92 -> #9c9ca4) one step brighter.
* chore(docs): add IBM Equal Access accessibility-checker setup
Add accessibility-checker as devDependency with aceconfig.js (policy
IBM_Accessibility, JSON reports in docs/a11y-results/, gitignored).
Scan with: npx achecker <url> against a built docs site.
* fix(docs): WCAG AA contrast and ARIA fixes across docs and API reference
Validated with IBM Equal Access scans (light theme): home, quickstart and
component pages at 0 violations; /api from 3382 down to 167 (all remaining
are Redoc-internal DOM: schema table headers, svg/select labels).
Docs site:
- Light primary pink #d11074 — passes 4.5:1 on white and inline-code bg
- Light Prism palette darkened per-token to pass 4.5:1 on #f9f9fd
- TabItem swizzled to give tabpanels an accessible name (aria-label)
- codeBlockA11y client module: scrollable code blocks get role=region +
unique label; non-scrollable ones lose the needless tabindex
API reference (Redocusaurus):
- redocA11y client module: role=main on api-content, role=navigation on
sidebar — fixes 1924 aria_content_in_landmark violations
- HTTP method badges and response chips darkened to pass with white text
- Light-theme overrides: accessible pink #cd1072 for links, required
markers, constraint chips, schema tree lines; darker grays for utility
buttons and type labels (incl. 0.7-opacity wrapper fix)
- Sidebar active/hover items use regular text color, method badges keep
their own colors; expandable property names match non-expandable ones
* fix(docs): WCAG AA contrast fixes for dark theme
Validated with IBM Equal Access scans in dark mode (temporary
colorMode.defaultMode flip during scanning): home, quickstart and
component pages at 0 violations; /api matches light at 167 remaining
(all Redoc-internal DOM: table headers, svg/select labels).
- Code block comments/line numbers #4a5060 -> #798197 (4.56:1 on #18181a)
- Redoc dark sample tokens: boolean/null #e95c59, number #5392b8
- Status-code tabs: lift docusaurus-theme-redoc's #303846 !important
selected-tab rule with a higher-specificity override
- oneOf variant buttons: dark text in both themes (their white/pink
backgrounds are theme-independent)
- redocA11y client module: patch response chip colors (success green /
error red) to dark-accessible variants when data-theme=dark — a single
Redoc theme color cannot pass on both light and dark derived
backgrounds, and status is only distinguishable by computed color
* fix(docs): resolve remaining Redoc-internal accessibility violations
Extend the redocA11y client module with semantic patches for Redoc DOM
the theme cannot reach (validated: 0 IBM Equal Access violations on all
scanned pages in both light and dark themes):
- Decorative svg chevrons/arrows: aria-hidden=true (svg_graphics_labelled)
- Content-type dropdowns: aria-label (input_label_exists)
- Schema field tables (2-col name|description layout, no <th> anywhere):
role=presentation — content reads in DOM order; role=rowheader on <td>
is invalid ARIA inside a native table (table_headers_exists/related)
- Semantic patches run on the next animation frame after DOM changes so
Redoc's lazy-rendered operations are covered immediately; the heavier
color patch stays debounced
* ci(docs): gate docs accessibility with IBM Equal Access scans
Add test-docs-accessibility job to docs_test.yml (rides the existing
docs/** path filter from ci.yml): build + scan 4 representative pages
(home, quickstart, component page, /api) in light theme, then flip
colorMode.defaultMode to dark, rebuild and scan again.
- scripts/a11y-ci.sh: serves the build and runs npx achecker per page
with one retry to absorb Redoc lazy-render timing flakes; any real
violation fails the job (failLevels: violation)
- aceconfig.js: pin ruleArchive to 19May2026 so IBM rule updates don't
break CI without a deliberate bump
* ci(docs): fix Chrome sandbox launch on Ubuntu 24.04 runners
Ubuntu 24.04 restricts unprivileged user namespaces via AppArmor, which
prevents puppeteer's Chrome (used by the IBM checker) from starting its
sandbox. Re-enable them with the documented sysctl workaround instead of
weakening the browser with --no-sandbox.
* fix(docs): align response status code with description text
Redoc sets vertical-align: top and a smaller line-height on the status
code <strong> inside response buttons, leaving "200" visually higher
than "Successful Response". Align both to the shared text baseline.
* refactor(docs): apply PR review feedback
- redocA11y: match only real Redoc routes (/api, /api/workflow) — a bare
startsWith("/api") also matched docs pages like /api-request and
leaked one body MutationObserver per navigation
- codeBlockA11y: also observe the hidden attribute — Docusaurus tabs
toggle panels via hidden (no childList mutation), so scrollable blocks
inside an initially hidden tab were never re-evaluated for tabindex
- Extract Prism themes to src/prismThemes.js (docusaurus.config.js was
past the 600-line red flag)
- concepts-components.mdx (current + 1.9.0 + 1.8.0): comment pointing
hardcoded snippets to recursive_character.py to mitigate drift
Validated: clean build + IBM Equal Access scans 4/4 passing.
* replace-openapi-file-with-1.10
* migrate-prism-changes-to-1.10-version
* a11y-script-dont-block
---------
Co-authored-by: Mendon Kissling <59585235+mendonk@users.noreply.github.com>
fix(ci): resolve validate-version output in release-lfx changelog link
The create-release job references
needs.validate-version.outputs.current_version in its generated release
notes (the Full Changelog compare link), but validate-version was not in
the job's needs array, so the expression evaluated empty and the link
rendered as compare/v...lfx-vX.Y.Z (broken base).
Add validate-version to the create-release needs. This adds no real
serialization: create-release already waits on release-lfx, which
transitively requires validate-version via run-tests, and the job's
always() if-condition is unaffected.
Flagged by actionlint: property "validate-version" is not defined in
object type {build-docker, release-lfx}.
fix(ci): create GitHub releases on the dispatched v-prefixed tag
The create_release job passed the bare version (v stripped) as the
release tag with no commit target, so when that tag did not exist
GitHub minted a new lightweight tag at the default-branch HEAD -- the
wrong commit, still carrying the previous version (main only adopts a
release's version via the post-release back-merge). Every release since
1.8.2 shipped a stray bare tag (1.8.2, 1.8.3, 1.9.0-1.9.6, 1.10.0)
pointing at a previous-version commit, and the GitHub release had to be
manually re-pointed to the real vX.Y.Z tag after each release.
- create_release now attaches the release to inputs.release_tag for
stable releases; pre-releases keep their computed tag (e.g.
1.10.0rc1) but it is minted at the release commit via 'commit:'.
- release-lfx.yml pins the minted lfx-v* tag to github.sha instead of
the default branch.
The validate-tag-format guard (#12847) only blocks at dispatch time;
create_release was re-creating the very duplicates it guards against.
fix(ci): scope nightly migration-test pre-releases to the langflow stack
The previous fix (#13599) added a global --prerelease=allow, which let
UNRELATED dependencies resolve to alphas: on nightly run 27274206250
the stable->nightly upgrade pulled pydantic 2.14.0a1 + pydantic-yaml
1.6.1a1, and the pydantic alpha breaks langchain-core at import time
(RunnablePassthrough pydantic ValidationError), failing the nightly
boot right after a successful install. Clean installs were fine - only
this upgrade path resolved the alpha combo.
Scope pre-release eligibility to the langflow lockstep stack instead:
uv accepts a pre-release when the package's own requirement carries a
pre-release marker, but not via transitive pins, and the nightly chain
is langflow -> langflow-base -> lfx -> langflow-sdk with exact ==devN
pins. Request each directly; langflow-sdk versions independently
(0.2.0.devN), so an explicit .dev0 floor marks it eligible while lfx's
exact pin selects the version. The 'latest' branch gets the same
treatment via .dev0 floors on all four.
Verified by dry-run against PyPI: langflow/langflow-base/lfx at
1.11.0.dev2 + langflow-sdk 0.2.0.dev2 resolve with pydantic staying at
stable 2.13.4.
fix(ci): allow pre-releases when pinning the nightly in migration validation
Migration Test: pip/venv (stable -> nightly) failed deterministically on
nightly run 27260425158 (twice, including a rerun):
hint: langflow-base was requested with a pre-release marker (e.g.,
langflow-base==1.11.0.dev1), but pre-releases weren't enabled
(try: --prerelease=allow)
This is the first nightly publishing as a canonical .devN pre-release
of the langflow distribution (nightly -> stable bundle cutover). The
'latest' branch of the upgrade step already passes --prerelease=allow,
but the pinned-version branches do not. uv implicitly allows the
pre-release for the directly requested ==X.Y.Z.devN pin, yet langflow's
metadata pins langflow-base==X.Y.Z.devN transitively, and uv rejects
transitive pre-releases unless they are enabled - so the install fails
after the stable uninstall, sinking the migration test.
Add --prerelease=allow to both pinned-version install lines.
Integration tests failed twice in nightly run 27260425158 with pyleak
EventLoopBlockError - first Integration Tests 3.14, then 3.12 on the
rerun, each time in a different test. The blocking stack points at
refresh_models_dev_periodically: every app boot unconditionally starts
a lifespan task that immediately fetches https://models.dev/api.json,
so the request lands mid-test in whatever test happens to be running.
Under pyleak's asyncio debug instrumentation the fetch blocked the loop
0.797s against a 0.2s threshold. Whichever test draws the short straw
flakes - which is why it looked transient and moved between versions.
Add a LANGFLOW_MODELS_DEV_REFRESH env gate (default unchanged: enabled)
and disable it session-wide in the backend test conftest. Tests fall
back to the bundled static model lists, which is also deterministic.
Verified: with the gate set, app boot makes zero models.dev requests;
the previously failing integration test passes.
fix(ci): anchor langflow-base version extraction in nightly docker build
The first nightly on the 1.11 workspace (tag v1.11.0.dev0, run
27253229568) failed in Build Nightly Base Package within seconds:
'Base version format is incorrect'. The extraction (uv tree, grep
langflow-base unanchored, awk field 3, first line) broke because uv
tree now prints the langflow-base workspace-root line
('langflow-base v1.11.0.dev0', two fields) before any dependency line
('langflow-base[complete] v1.11.0.dev0' under the langflow root, three
fields), so the first match yields an empty field 3 and the format
check exits 1. uv tree's stderr is discarded, which made the real
cause invisible in CI.
Anchor to the root line and take field 2 instead - the exact pattern
the langflow (main package) extraction in this same file already uses,
which is why the main-package builds passed on the same tag. Verified
both extractions print 1.11.0.dev0 against a local checkout of
v1.11.0.dev0.
The test spawns a child via multiprocessing spawn context, which
cold-imports the full langflow package (plus coverage's multiprocessing
hooks in CI) before appending a single event. The 10s join timeout is
routinely exceeded on a loaded CI runner sharing 4 vCPUs with a second
xdist worker: in nightly run 27253229568 (Unit Tests - Python 3.12 -
Group 5) the test failed all 12 executions (5 reruns x 2 step attempts),
each rerun exactly 10s apart - the join deadline, not a product bug.
Raise the liveness bound to 60s (join returns immediately when the
child exits, so the passing case is unaffected) and kill the child on
timeout so a hung spawn can't leak into later tests.
Unit Tests - Python 3.12 - Group 3 has been failing at 98-99% on the
nightly: the job hits the nick-fields/retry 40-minute per-attempt timeout
and pytest is SIGTERM'd mid-test, so it looks like one flaky last test
when it is actually a deterministic timeout (and the internal retry plus
run-level retries can never succeed).
Root cause is twofold:
1. .test_durations was last regenerated ~May 2025 and covered only 2,219
of ~9,500 current unit tests, so pytest-split weighted 77% of the
suite at the 0.73s average. The expensive client-fixture tests
(test_webhook.py, test_login.py - each pays a full create_app +
lifespan boot per test, 60-120s late in a CI run) clustered into
group 3's tail, making it ~8-10 minutes slower than its siblings
(33:15 on 3.13, >40min on 3.12 which is additionally slowed by
astrapy disabling SSL connection reuse on Python 3.12.0-11).
The weekly store_pytest_durations workflow that should refresh the
file has been dying at the 6-hour GitHub job limit every week (serial
full-suite run no longer fits), so the file silently froze.
This regenerates the file from real per-test wall-clock measured in
the passing Python 3.13 nightly jobs (run 27246228762 attempt 1, all
5 groups, parsed from the -vv xdist logs: per-worker start-to-start
deltas). 9,508 tests now have measured durations; old entries are
kept where no new measurement exists. Simulated least_duration
split goes from one outlier group to 5 even groups (~24.6 min each)
with the >50s tests spread 1-2 per group instead of 7 in one.
2. timeout_minutes 40 -> 50 gives headroom for matrix-cell variance
(3.12 runs ~20% slower than 3.13) so a slightly slow cell degrades
gracefully instead of burning 2x40min and failing the whole run.
Follow-ups (not in this PR): fix store_pytest_durations to run with
xdist or split groups so it fits the 6h limit; investigate the in-worker
degradation that makes client-fixture boots cost 8-12s early in a run
but 60-120s after ~30 minutes.
The first 1.11.0.dev0 nightly failed its "Test Langflow Main CLI" step:
the fork bump's sync_bundle_lfx_pin.py re-synced every bundle floor to
lfx>=1.11.0, and PEP 440 sorts the nightly's 1.11.0.dev0 BELOW 1.11.0,
so the workspace-built bundles (whose metadata shadows the satisfiable
PyPI lfx-arxiv 0.1.1 during `uv pip install dist/*.whl`) reject the
branch's own lfx while langflow-base pins it exactly — unresolvable.
Floor at lfx>=X.Y.0.dev0,<(X+1).0.0 instead: X.Y.0.dev0 is the lowest
version PEP 440 admits in the minor line, so every devN / rcN / final
satisfies the floor while older lines and the next major stay excluded.
This closes the NIGHTLY.md activation-gate hole structurally — future
minor forks re-sync to a floor their own nightlies already satisfy.
- sync_bundle_lfx_pin.py: lfx_floor_spec emits the .dev0 floor
- port_bundle.py: mirrored _current_lfx_floor kept in step
- bundle pyprojects restamped via the script (arxiv/docling/duckduckgo/ibm)
- test_bundle_lfx_pin.py expectations updated (20/20 passing)
- NIGHTLY.md gate section annotated with the post-activation fix
uv.lock is unaffected (workspace lfx is recorded as an editable source
with no specifier — which is also why uv sync/lock passed in the same
job). The release.yml RC floor-relax sed still matches the new form;
now redundant but harmless. No bundle version bump needed: published
0.1.1 floors >=1.10.0.rc0, satisfiable by the whole 1.11 line.
The release-1.11.0 fork bumped lfx to 1.11.0 but left component_index.json's version field at 1.10.0. _read_component_index fails closed on exact version mismatch, so the bundled registry loads as None and the upgrade-gate tests fail (UpgradeFlowError: 'bundled component registry is empty or missing') across the LFX test matrix. That failure blocks the nightly's release-nightly-build job, so no canonical 1.11.0.devN is published and check-nightly-status blocks CI everywhere.
Surgical restamp: version -> 1.11.0 and recomputed sha256 (same orjson OPT_SORT_KEYS hashing and OPT_SORT_KEYS|OPT_INDENT_2 serialization as scripts/build_component_index.py). Entries unchanged. Verified the reader validation passes and the three failing tests go green.