Skip to content

fix(components): follow HTTP redirects in URL component with per-hop SSRF revalidation#13572

Merged
erichare merged 5 commits into
release-1.10.1from
fix/url-component-follow-redirects
Jun 10, 2026
Merged

fix(components): follow HTTP redirects in URL component with per-hop SSRF revalidation#13572
erichare merged 5 commits into
release-1.10.1from
fix/url-component-follow-redirects

Conversation

@erichare

@erichare erichare commented Jun 9, 2026

Copy link
Copy Markdown
Collaborator

Problem

The URL component sent every request with follow_redirects=False. Any site whose entered URL 301s to its canonical address — http→https upgrades and www/non-www normalization, i.e. the default behavior of most WordPress/nginx/CDN-fronted sites — was scraped incorrectly:

URL entered Old behavior
www.example-site.com Scrapes the literal redirect stub (301 Moved Permanently / nginx, ~52 chars) as the page text — no error, just garbage content
http://example-site.com/ Hard failure (No documents were successfully loaded) when the 301 body is empty
exact canonical URL Worked

Reported against a real site (schemasauce.com, a static WordPress site) where only the exact canonical form https://schemasauce.com/ scraped correctly.

follow_redirects=False was deliberate (OWASP SSRF guidance: a validated public URL could redirect to an internal address), so the fix must not simply re-enable auto-follow.

Fix

Follow redirects without giving up the SSRF guarantee, mirroring the pattern already shipped in the API Request component:

  • New advanced Follow Redirects input, default on.
  • When SSRF protection is enabled (the default), redirects are followed manually: each Location (relative ones resolved against the current URL) is re-run through ensure_url() — the same private/loopback/link-local/metadata denylist and DNS pinning as the initial request — before any connection to it is made. A blocked hop raises (or is skipped under Continue on Failure).
  • httpx never auto-follows on this path, so no hop can reach an address that wasn't validated and pinned.
  • Cross-host hops drop Authorization/Proxy-Authorization/Cookie headers (same as httpx's native behavior); chains are capped at 20 redirects.
  • With SSRF protection disabled there is no pin to bypass, so httpx follows redirects natively.
  • Setting Follow Redirects = off restores the previous single-request behavior exactly.

Existing saved flows embed the previous component code and are unaffected until the component is updated.

Verification

Against the live reporter site with SSRF protection enabled: www.schemasauce.com, http://schemasauce.com/, and https://schemasauce.com/ now all resolve to the canonical page and extract the full content (3,664 chars) where the first two previously returned the 52-char nginx stub / errored.

Test plan

  • New regression test test_url_component_follows_redirects_with_per_hop_pinning: 301 chain is followed to the final page; both hops connect only to the validated, pinned public IP; the redirect target is DNS-resolved exactly once (validation only), so a rebinding flip after validation has no effect.
  • New regression test test_url_component_blocks_redirect_to_internal_ip: a 302 from a public host to a localhost-resolving host raises SSRF Protection before any connection to the internal target.
  • New input-config test: follow_redirects defaults to True.
  • All 7 pre-existing URL-component DNS-rebinding tests pass unchanged; full test_dns_rebinding.py + backend test_url_component.py: 40 passed.
  • lfx isolated unit tests (uv run pytest in src/lfx): 3 passed.
  • ruff check / ruff format clean; pre-commit hooks pass.

Notes for CI

component_index.json (URLComponent code_hash) and the starter projects embedding the component source are intentionally left to the autofix jobs / gp-backend-check to regenerate on this branch. locales/en.json was regenerated locally via scripts/gp/extract_backend_strings.py (adds the two keys for the new input).

Summary by CodeRabbit

  • New Features

    • Added follow_redirects option to the URL component (enabled by default).
    • Redirects are now followed securely with per-hop validation and DNS pinning under SSRF protection.
    • Sensitive headers are dropped when redirects cross different hosts.
  • Documentation

    • Updated locale documentation to explain follow_redirects behavior and SSRF protection mechanisms.

@coderabbitai

coderabbitai Bot commented Jun 9, 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: b988a982-1e1c-471b-a592-53016e85deec

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

URLComponent now accepts a follow_redirects input that enables SSRF-safe redirect following. When SSRF protection is active, redirects are manually re-validated per hop with DNS pinning; otherwise httpx handles redirects natively. Response processing is refactored into a shared helper, and sensitive headers are dropped across host boundaries. Two new SSRF-protection tests verify per-hop pinning and internal-IP blocking.

Changes

Add SSRF-safe redirect following to URLComponent

Layer / File(s) Summary
Input contract and redirect constants
src/lfx/src/lfx/components/data_source/url.py
URLComponent.inputs extends with a new follow_redirects Boolean input (default True, advanced). Module constants define HTTP redirect status codes and maximum redirect hop budget.
Response processing and redirect header management
src/lfx/src/lfx/components/data_source/url.py
Response handling is centralized in _process_response (status checking, CSS filtering, HTML extraction, metadata). _headers_for_redirect manages sensitive header drops across hostname changes during redirects.
Core redirect implementation
src/lfx/src/lfx/components/data_source/url.py
_fetch_with_revalidated_redirects implements manual redirect loop with per-hop DNS pinning and re-validation via ensure_url; _fetch_url_with_pinning branches based on follow_redirects and SSRF protection status to either use the revalidated path or defer to httpx.
Input default test and localization
src/lfx/tests/unit/components/data_source/test_url_component.py, src/backend/base/langflow/locales/en.json
Unit test verifies follow_redirects input defaults to True. Locale strings document the redirect behavior and SSRF protection re-validation per hop.
SSRF redirect behavior integration tests
src/backend/tests/unit/components/data_source/test_dns_rebinding.py
Two new tests verify per-hop DNS pinning when follow_redirects=True (resolution only for hops, both connections pinned), and SSRF blocking of redirects to internal IPs before connection.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • langflow-ai/langflow#13394: Implements SSRF-safe manual redirect following with per-hop revalidation/DNS pinning in APIRequestComponent, using the same pattern as this PR's URLComponent redirect logic.
  • langflow-ai/langflow#13488: Introduces the ensure_url DNS-pinning/SSRF re-validation flow that this PR reuses for per-hop redirect validation, and extends the test_dns_rebinding.py suite that this PR adds tests to.

Suggested labels

bug, lgtm

Suggested reviewers

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

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Test Quality And Coverage ⚠️ Warning Tests cover core SSRF/pinning but lack coverage for header dropping on redirects, MAX_REDIRECTS limits, relative URLs, crawl base after redirects, and follow_redirects=False behavior. Add tests for: header dropping on origin changes, redirect limit enforcement, relative Location resolution, crawl base URL after redirects (review comment), follow_redirects=False, SSRF disabled mode.
✅ Passed checks (8 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly and clearly summarizes the main changes: adding HTTP redirect following to the URL component with per-hop SSRF revalidation, which is exactly what the PR implements.
Docstring Coverage ✅ Passed Docstring coverage is 93.33% 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 Three new tests added for follow_redirects feature with proper assertions, mocking, and error handling. Tests follow conventions and directly test the new functionality.
Test File Naming And Structure ✅ Passed Test files follow test_*.py naming, proper pytest structure, descriptive test names, and cover positive/negative scenarios with good edge case coverage and test isolation.
Excessive Mock Usage Warning ✅ Passed Mocks appropriately target only external I/O (DNS, network) not core logic. Security tests for redirects and SSRF justifiably use controlled mocking without excessive abstraction.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/url-component-follow-redirects

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 9, 2026
@github-actions

github-actions Bot commented Jun 9, 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 9, 2026
@erichare erichare changed the base branch from main to release-1.11.0 June 9, 2026 21:54
@github-actions github-actions Bot added bug Something isn't working and removed bug Something isn't working labels Jun 9, 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: 2

🧹 Nitpick comments (1)
src/backend/tests/unit/components/data_source/test_dns_rebinding.py (1)

753-753: ⚡ Quick win

Remove unnecessary @pytest.mark.asyncio decorator.

Based on learnings, the project configures asyncio_mode = 'auto' in pyproject.toml, which means pytest-asyncio automatically detects and runs async tests without requiring explicit decorators. The decorator should be removed to follow the project convention.

This applies to both new test methods in this class.

♻️ Proposed fix
-    `@pytest.mark.asyncio`
     async def test_url_component_follows_redirects_with_per_hop_pinning(self):

Apply the same change to test_url_component_blocks_redirect_to_internal_ip at line 822.

🤖 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/components/data_source/test_dns_rebinding.py` at line
753, Remove the unnecessary `@pytest.mark.asyncio` decorator from the async test
method where it's currently applied (the test at the decorator on the snippet
around line 753) and also from the
test_url_component_blocks_redirect_to_internal_ip test; both async tests should
rely on the project's asyncio_mode='auto' detection instead of explicit
decorators—locate the decorator usages above the test function definitions and
delete those `@pytest.mark.asyncio` lines.

Source: Learnings

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

Inline comments:
In `@src/lfx/src/lfx/components/data_source/url.py`:
- Around line 311-324: The current _headers_for_redirect uses only hostname to
decide whether to drop sensitive headers, which misses origin changes like http
vs https or different ports; modify _headers_for_redirect to compare full origin
(scheme, hostname, port) between current_url and next_url: parse both with
urlparse, derive explicit ports (use 80 for http and 443 for https when port is
None), and only retain all headers when scheme, hostname, and resolved port all
match; otherwise filter out the sensitive set
{"authorization","proxy-authorization","cookie"} as currently done.

---

Nitpick comments:
In `@src/backend/tests/unit/components/data_source/test_dns_rebinding.py`:
- Line 753: Remove the unnecessary `@pytest.mark.asyncio` decorator from the async
test method where it's currently applied (the test at the decorator on the
snippet around line 753) and also from the
test_url_component_blocks_redirect_to_internal_ip test; both async tests should
rely on the project's asyncio_mode='auto' detection instead of explicit
decorators—locate the decorator usages above the test function definitions and
delete those `@pytest.mark.asyncio` lines.
🪄 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: 69669bca-9ab4-4db9-be51-2ff6d2d6852e

📥 Commits

Reviewing files that changed from the base of the PR and between 13a937c and cf2aa6b.

📒 Files selected for processing (4)
  • src/backend/base/langflow/locales/en.json
  • src/backend/tests/unit/components/data_source/test_dns_rebinding.py
  • src/lfx/src/lfx/components/data_source/url.py
  • src/lfx/tests/unit/components/data_source/test_url_component.py

Comment thread src/lfx/src/lfx/components/data_source/url.py
Comment thread src/lfx/src/lfx/components/data_source/url.py
@github-actions github-actions Bot added bug Something isn't working and removed bug Something isn't working labels Jun 9, 2026
@codecov

codecov Bot commented Jun 9, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
⚠️ Please upload report for BASE (release-1.10.1@2df7c22). Learn more about missing BASE report.

Additional details and impacted files

Impacted file tree graph

@@                Coverage Diff                @@
##             release-1.10.1   #13572   +/-   ##
=================================================
  Coverage                  ?   58.40%           
=================================================
  Files                     ?     2302           
  Lines                     ?   219145           
  Branches                  ?    31137           
=================================================
  Hits                      ?   127998           
  Misses                    ?    89693           
  Partials                  ?     1454           
Flag Coverage Δ
backend 65.14% <ø> (?)
frontend 57.71% <ø> (?)
lfx 54.31% <ø> (?)

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

🚀 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 9, 2026

Copy link
Copy Markdown
Contributor

Frontend Unit Test Coverage Report

Coverage Summary

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

Unit Test Results

Tests Skipped Failures Errors Time
4940 0 💤 0 ❌ 0 🔥 14m 16s ⏱️

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

Copy link
Copy Markdown
Collaborator Author

Re the nitpick about removing @pytest.mark.asyncio from the two new tests in test_dns_rebinding.py: intentionally skipped. All 21 async tests in that file carry the explicit decorator, so the new tests follow the existing file convention — removing only the two new ones would leave the file inconsistent, and the decorator is a harmless no-op under asyncio_mode = "auto". Happy to drop it file-wide in a separate cleanup if preferred.

@github-actions github-actions Bot added the bug Something isn't working label Jun 10, 2026
erichare added 2 commits June 10, 2026 10:42
…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.
…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.
@erichare erichare force-pushed the fix/url-component-follow-redirects branch from 3e96934 to 1f25c47 Compare June 10, 2026 17:43
@erichare erichare changed the base branch from release-1.11.0 to release-1.10.1 June 10, 2026 17:43
@erichare

Copy link
Copy Markdown
Collaborator Author

Rebased onto release-1.10.1 and retargeted the PR base (was release-1.11.0, which had grown a conflict in the generated files). The two source commits cherry-picked cleanly — url.py/api_request.py/tests/en.json are identical between the two bases — and the three [autofix.ci] commits were dropped since their component_index.json / starter-project content was generated for the old base; autofix will regenerate them for this branch. All suites re-verified on the new base: backend 91 passed / 4 skipped, lfx isolated 9 passed.

@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 bug Something isn't working label Jun 10, 2026
…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.
@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
@github-actions github-actions Bot added bug Something isn't working and removed bug Something isn't working labels Jun 10, 2026
@erichare erichare merged commit 9a86838 into release-1.10.1 Jun 10, 2026
124 checks passed
@erichare erichare deleted the fix/url-component-follow-redirects branch June 10, 2026 19:45
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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant