Skip to content

fix: limit public flow endpoint from displaying private flow streams - #13602

Merged
Jkavia merged 5 commits into
release-1.10.1from
security/tmp-build-missing-auth
Jun 10, 2026
Merged

fix: limit public flow endpoint from displaying private flow streams#13602
Jkavia merged 5 commits into
release-1.10.1from
security/tmp-build-missing-auth

Conversation

@Jkavia

@Jkavia Jkavia commented Jun 10, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Fixes unauthenticated access to private-flow job streams via the
    build_public_tmp endpoints. GET /build_public_tmp/{job_id}/events and
    POST /build_public_tmp/{job_id}/cancel previously accepted any
    job_id without verifying it belonged to a public build — an attacker
    with a known or guessed job_id could read another user's live event
    stream (LLM output, API keys, internal data) or cancel an in-flight
    private build.
  • Adds a public job registry: register_public_job(job_id) is called
    immediately after a job is started via build_public_tmp. Both public
    endpoints now call _assert_public_job(job_id, queue_service) first,
    which returns HTTP 404 "Job not found" (not 403, to avoid confirming
    the job exists under a different access tier) if the job was never
    registered as public.
  • JobQueueService gets register_public_job / is_public_job (sync) /
    is_public_job_async (async, uniform interface for route handlers).
    RedisJobQueueService overrides register_public_job to also persist
    langflow:public_job:{job_id} to Redis, and is_public_job_async checks
    local memory first then falls back to Redis for cross-worker correctness.
    cleanup_job discards the registration (and deletes the Redis key) so
    finished jobs can't be queried after eviction.

Security

  • Vulnerability: unauthenticated read access to private build event
    streams + unauthenticated denial-of-service via build cancellation.
  • Fix: allow-list of job_ids registered through the public build path,
    enforced before any event/cancel handling on both unauthenticated routes.
  • Manually verified via curl: a private (authenticated) job's job_id
    hits the public events endpoint and gets {"detail":"Job not found"}.

Test plan

  • test_job_queue_service_register_and_check_public_job
  • test_job_queue_service_unregistered_job_not_public
  • test_job_queue_service_is_public_job_async_base
  • test_job_queue_service_cleanup_removes_public_registration
  • test_private_job_id_blocked_on_public_events_endpoint — adversarial,
    proves a private job_id is rejected (404) on the public events route
  • test_private_job_id_blocked_on_public_cancel_endpoint — adversarial,
    proves a private job_id is rejected (404) on the public cancel route
  • test_redis_public_job_cross_worker_fallback — Worker B with empty
    local memory falls back to Redis to confirm a job registered by Worker A
  • test_redis_cleanup_removes_public_job_key — Redis public_job key is
    deleted on cleanup
  • make format_backend + uv run ruff check clean on all changed files
  • All 8 new/updated tests pass (uv run pytest ... — 0 failures)

Test with public Job Id

Screenshot 2026-06-09 at 4 23 24 PM

Test with private Job Id

Screenshot 2026-06-09 at 4 22 55 PM

Summary by CodeRabbit

  • Bug Fixes

    • Enhanced security for public build endpoints to prevent unauthorized access to private builds via job ID guessing.
    • Public endpoints now return HTTP 404 when accessed with unregistered job IDs.
  • Tests

    • Added security regression tests verifying private builds remain inaccessible through public endpoints.
    • Added tests for public job tracking and cross-worker validation.

@coderabbitai

coderabbitai Bot commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Important

Review skipped

Auto incremental reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 40ffa4c9-9115-40e1-af52-a4f31855cf58

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

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Walkthrough

This PR secures unauthenticated build endpoints by implementing a public-job registry that tracks which jobs were created via the public API. Jobs are marked public only when built through the public endpoint, and both event streaming and cancellation are guarded to reject requests for private jobs with HTTP 404.

Changes

Public Build Job Access Control

Layer / File(s) Summary
Public-job tracking service infrastructure
src/backend/base/langflow/services/job_queue/service.py
Adds in-memory _public_jobs set and Redis key management to JobQueueService. Base class provides register_public_job, is_public_job, and is_public_job_async with synchronous set lookup. RedisJobQueueService overrides public-job methods to persist markers with TTL and fallback to Redis EXISTS for cross-worker consistency. Cleanup removes public registrations from both memory and Redis.
Public endpoint access control with job verification
src/backend/base/langflow/api/v1/chat.py
Introduces _assert_public_job guard that rejects requests for unregistered job IDs with HTTP 404. build_public_tmp registers jobs as public after creation. Both get_build_events_public and cancel_build_public call the guard before proceeding.
Unit tests for public-job registration semantics
src/backend/tests/unit/test_chat_endpoint.py (lines 1322–1381)
Tests JobQueueService public-job API: register/query behavior, unregistered jobs returning false, sync/async consistency, and cleanup removing public state.
Security regression tests for endpoint access control
src/backend/tests/unit/test_chat_endpoint.py (lines 1382–1446)
Verifies private build jobs cannot be accessed via unauthenticated public endpoints (build_public_tmp/{job_id}/events and build_public_tmp/{job_id}/cancel), both returning HTTP 404.
Redis cross-worker public-job verification tests
src/backend/tests/unit/test_redis_job_queue_service.py
Tests Redis-backed public-job semantics across simulated workers: in-memory fallback to Redis for correctness, and cleanup removing Redis public-job markers.

Sequence Diagram

sequenceDiagram
    participant Client
    participant build_public_tmp
    participant get_build_events_public
    participant _assert_public_job
    participant queue_service
    Client->>build_public_tmp: POST build request
    build_public_tmp->>queue_service: register_public_job(job_id)
    Client->>get_build_events_public: GET events for job_id
    get_build_events_public->>_assert_public_job: verify job_id
    _assert_public_job->>queue_service: is_public_job_async(job_id)
    alt Job is public
        queue_service-->>_assert_public_job: True
        _assert_public_job-->>get_build_events_public: OK
        get_build_events_public-->>Client: Build events
    else Job not public
        queue_service-->>_assert_public_job: False
        _assert_public_job-->>get_build_events_public: HTTPException 404
        get_build_events_public-->>Client: Job not found
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Suggested labels

bug, lgtm

Suggested reviewers

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

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Test Quality And Coverage ⚠️ Warning Security tests lack client.cookies.clear() before public endpoint calls, so they don't validate true unauthenticated scenarios; missing test for successful public build access. Add client.cookies.clear() in both security tests before public endpoint calls; add test validating public build successfully accesses public endpoints.
✅ Passed checks (8 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title accurately describes the main security fix: preventing public flow endpoints from accessing private flow job streams through an unauthenticated path.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Test Coverage For New Implementations ✅ Passed PR includes 8 new tests covering JobQueueService methods, security regression tests, and cross-worker behavior. Tests follow naming conventions and invoke actual implementations, not placeholders.
Test File Naming And Structure ✅ Passed All test files follow correct structure: test_*.py naming, pytest in unit tests, descriptive names, docstrings, fixtures, positive/negative scenarios, edge cases, and proper setup/teardown.
Excessive Mock Usage Warning ✅ Passed New tests avoid excessive mocking. Unit tests use real service objects; integration tests use real client fixtures. FakeRedis (test double) enables cross-worker testing without mocks.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch security/tmp-build-missing-auth

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions github-actions Bot added the bug Something isn't working label Jun 10, 2026
@github-actions

github-actions Bot commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

✅ Test Coverage Advisor

No source changes detected without accompanying tests. Thanks for keeping coverage up! 🎉

Advisory check only — never blocks merge.

@github-actions github-actions Bot added bug Something isn't working and removed bug Something isn't working labels Jun 10, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (2)
src/backend/tests/unit/test_chat_endpoint.py (1)

1371-1376: ⚡ Quick win

Avoid binding test behavior to private _queues tuple internals

Line 1375 manually writes svc._queues[job_id] with a private tuple shape. Prefer seeding through JobQueueService.create_queue(job_id) so this contract test doesn’t break on unrelated internal tuple/layout refactors.

Suggested patch
-    svc._queues[job_id] = (asyncio.Queue(), None, None, None)  # type: ignore[arg-type]
+    svc.create_queue(job_id)
     await svc.cleanup_job(job_id)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/backend/tests/unit/test_chat_endpoint.py` around lines 1371 - 1376, The
test seeds a queue by directly mutating the private svc._queues tuple shape
which couples the test to internal implementation; instead call the public queue
creation API (e.g. JobQueueService.create_queue or svc.create_queue(job_id)) to
create the minimal queue entry before invoking svc.cleanup_job(job_id), so the
test uses the public contract rather than writing to svc._queues and remains
resilient to internal tuple/layout changes while still reaching the
unconditional _public_jobs.discard path.
src/backend/tests/unit/test_redis_job_queue_service.py (1)

2473-2475: ⚡ Quick win

Avoid seeding svc._queues with a private tuple shape directly.

Line 2474 hard-codes an internal tuple contract and needs type: ignore, which makes this test fragile to service-internal refactors. Prefer creating the queue through the service API and then invoking cleanup.

Proposed refactor
-        # Seed a minimal queue entry so cleanup_job doesn't early-return
-        svc._queues[job_id] = (asyncio.Queue(), None, None, None)  # type: ignore[arg-type]
+        # Seed via service API so the test doesn't depend on private tuple shape
+        svc.create_queue(job_id)
         await svc.cleanup_job(job_id)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/backend/tests/unit/test_redis_job_queue_service.py` around lines 2473 -
2475, The test seeds svc._queues[job_id] with a private tuple shape which
couples the test to implementation details; instead, create the minimal queue
via the service's public API (e.g., call whatever method exists to
create/open/enqueue for a job) so the queue entry is added by the service
itself, then call svc.cleanup_job(job_id); remove the direct assignment to
svc._queues and the type: ignore. Reference: replace the direct manipulation of
svc._queues and keep using svc.cleanup_job(job_id) with job_id.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/backend/base/langflow/services/job_queue/service.py`:
- Around line 1637-1639: The public-marker must be persisted to Redis
synchronously so other workers don't see a false 404; in register_public_job
replace the background write with a synchronous persistence step: instead of
calling self._spawn_background(self._set_public_job_key(job_id)), call the
Redis-write path directly (await or call the function
_set_public_job_key(job_id) synchronously) so the marker is stored before
register_public_job returns, keeping _assert_public_job semantics consistent
across workers.
- Around line 1641-1645: The public-job Redis marker is set once with
ex=self._ttl in _set_public_job_key but never renewed, so when long-running
builds refresh the stream/owner TTL the public marker should be refreshed too;
update the code to refresh the same key whenever you refresh the stream or owner
TTL (i.e., call _set_public_job_key(job_id) or call
self._client.expire(self._public_job_key(job_id), self._ttl) from the same
places that call _refresh_stream_ttl / _refresh_owner_key or where you currently
call expire on stream/owner keys) so the public marker stays alive for the
duration of the build.

---

Nitpick comments:
In `@src/backend/tests/unit/test_chat_endpoint.py`:
- Around line 1371-1376: The test seeds a queue by directly mutating the private
svc._queues tuple shape which couples the test to internal implementation;
instead call the public queue creation API (e.g. JobQueueService.create_queue or
svc.create_queue(job_id)) to create the minimal queue entry before invoking
svc.cleanup_job(job_id), so the test uses the public contract rather than
writing to svc._queues and remains resilient to internal tuple/layout changes
while still reaching the unconditional _public_jobs.discard path.

In `@src/backend/tests/unit/test_redis_job_queue_service.py`:
- Around line 2473-2475: The test seeds svc._queues[job_id] with a private tuple
shape which couples the test to implementation details; instead, create the
minimal queue via the service's public API (e.g., call whatever method exists to
create/open/enqueue for a job) so the queue entry is added by the service
itself, then call svc.cleanup_job(job_id); remove the direct assignment to
svc._queues and the type: ignore. Reference: replace the direct manipulation of
svc._queues and keep using svc.cleanup_job(job_id) with job_id.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 5d075916-d21e-44a3-80e5-ab87b2a2931f

📥 Commits

Reviewing files that changed from the base of the PR and between 481cdd3 and a6da856.

📒 Files selected for processing (4)
  • src/backend/base/langflow/api/v1/chat.py
  • src/backend/base/langflow/services/job_queue/service.py
  • src/backend/tests/unit/test_chat_endpoint.py
  • src/backend/tests/unit/test_redis_job_queue_service.py

Comment on lines +1637 to +1639
super().register_public_job(job_id)
if self._client:
self._spawn_background(self._set_public_job_key(job_id))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Persist the public marker before returning the job_id.

This only updates local memory synchronously; the Redis marker is written later in a background task. In a multi-worker deployment, the first public /events or /cancel request can land on another worker before that task runs, so _assert_public_job returns a false 404 for a legitimate public build. The cross-worker test already has to drain svc_a._background_tasks before worker B can see the marker, but the HTTP path has no equivalent barrier.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/backend/base/langflow/services/job_queue/service.py` around lines 1637 -
1639, The public-marker must be persisted to Redis synchronously so other
workers don't see a false 404; in register_public_job replace the background
write with a synchronous persistence step: instead of calling
self._spawn_background(self._set_public_job_key(job_id)), call the Redis-write
path directly (await or call the function _set_public_job_key(job_id)
synchronously) so the marker is stored before register_public_job returns,
keeping _assert_public_job semantics consistent across workers.

Comment thread src/backend/base/langflow/services/job_queue/service.py
Comment thread src/backend/tests/unit/test_chat_endpoint.py
@codecov

codecov Bot commented Jun 10, 2026

Copy link
Copy Markdown

Codecov Report

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

Files with missing lines Patch % Lines
...ackend/base/langflow/services/job_queue/service.py 78.12% 7 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@                Coverage Diff                @@
##             release-1.10.1   #13602   +/-   ##
=================================================
  Coverage                  ?   58.60%           
=================================================
  Files                     ?     2302           
  Lines                     ?   219919           
  Branches                  ?    32365           
=================================================
  Hits                      ?   128875           
  Misses                    ?    89585           
  Partials                  ?     1459           
Flag Coverage Δ
backend 65.11% <81.57%> (?)
frontend 58.00% <ø> (?)
lfx 54.34% <ø> (?)

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

Files with missing lines Coverage Δ
src/backend/base/langflow/api/v1/chat.py 51.00% <100.00%> (ø)
...ackend/base/langflow/services/job_queue/service.py 79.76% <78.12%> (ø)
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@github-actions

github-actions Bot commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

Frontend Unit Test Coverage Report

Coverage Summary

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

Unit Test Results

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

@Jkavia
Jkavia force-pushed the security/tmp-build-missing-auth branch from bac3d90 to 4b95324 Compare June 10, 2026 16:52
@github-actions github-actions Bot added bug Something isn't working and removed bug Something isn't working labels Jun 10, 2026
@github-actions github-actions Bot added bug Something isn't working and removed bug Something isn't working labels Jun 10, 2026
@Jkavia
Jkavia requested review from Adam-Aghili and dkaushik94 June 10, 2026 17:30
@github-actions github-actions Bot added the lgtm This PR has been approved by a maintainer label Jun 10, 2026
# Gate the public events/cancel endpoints to jobs that were actually
# started through this public build path, preventing unauthenticated
# callers from reading or cancelling private-flow builds by job_id.
await queue_service.register_public_job(job_id)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Might be worth moving this to before the job is actually started..? @Jkavia

This might prove to be more of a nitpick rather than a significant improvement. The rationale being, what if a person triggers cancel while the flow is running and the flow has not been registered as public. There is a window of vulnerability. If we first register and mark the Id as public, we should be safe from there on out. Thoughts?

@Jkavia Jkavia Jun 10, 2026

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

the thing is job_id doesn't exist before start_flow_build runs. also unless we return the job Id there would be no way to cancel and by that ordering this will always execute prior to cancel endpoint being invoked

@github-actions github-actions Bot removed the lgtm This PR has been approved by a maintainer label Jun 10, 2026
@github-actions github-actions Bot added bug Something isn't working and removed bug Something isn't working labels Jun 10, 2026
@github-actions github-actions Bot added the lgtm This PR has been approved by a maintainer label Jun 10, 2026
@github-actions github-actions Bot added bug Something isn't working and removed bug Something isn't working labels Jun 10, 2026
@Jkavia
Jkavia added this pull request to the merge queue Jun 10, 2026
Merged via the queue into release-1.10.1 with commit 65daeff Jun 10, 2026
122 checks passed
@Jkavia
Jkavia deleted the security/tmp-build-missing-auth branch June 10, 2026 22:35
erichare added a commit that referenced this pull request Jun 17, 2026
* 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: #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>
severfire pushed a commit to severfire/langflow that referenced this pull request Jun 18, 2026
…ling (langflow-ai#13293)

* 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#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 (langflow-ai#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 langflow-ai#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 (langflow-ai#13639)

* chore: bump version to 1.10.1

* fix(lfx): restamp component index version after 1.10.1 fork bump (langflow-ai#13575)

Companion to the release-1.11.0 fix (langflow-ai#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) (langflow-ai#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) (langflow-ai#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) (langflow-ai#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) (langflow-ai#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) (langflow-ai#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) (langflow-ai#13605)

fix(ci): scope nightly migration-test pre-releases to the langflow stack

The previous fix (langflow-ai#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) (langflow-ai#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 (langflow-ai#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) (langflow-ai#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#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 (langflow-ai#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 (langflow-ai#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 langflow-ai#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 (langflow-ai#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 (langflow-ai#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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working lgtm This PR has been approved by a maintainer

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants