Skip to content

feat: upgrade Flask 2.3.3 → 3.1.3 - #7724

Open
wtfiwtz wants to merge 3 commits into
getredash:masterfrom
orchestrated-io:feat/flask3
Open

feat: upgrade Flask 2.3.3 → 3.1.3#7724
wtfiwtz wants to merge 3 commits into
getredash:masterfrom
orchestrated-io:feat/flask3

Conversation

@wtfiwtz

@wtfiwtz wtfiwtz commented Jun 1, 2026

Copy link
Copy Markdown
Contributor

Summary

Upgrade Flask 2.3.3 → 3.1.3, stacked on the refreshed SQLAlchemy 1.4 branch.

Rebased onto #7723 (itself on slimmed #7719).

Dependency upgrades

  • flask: 2.3.3 → 3.1.3
  • blinker: 1.6.2 → 1.9.0
  • flask-login / flask-wtf / pyjwt / supervisor companion bumps

Code

  • Flask 3 CSRF / talisman CSP dict handling
  • CLI RQ soft-import for supervisor_checks
  • query_results cleanup subquery fix for Postgres

Depends on

How is this tested?

  • Unit tests (pytest, jest)

@wtfiwtz

wtfiwtz commented Jun 1, 2026

Copy link
Copy Markdown
Contributor Author

Disclaimer: This comment was AI-generated to summarize the CVE and advisory coverage for this PR. Please verify against your scanner output and release notes before treating it as authoritative.

CVE / advisory coverage for PR #7724 (feat/flask3)

This PR is stacked on #7723 and #7719 — the branch contains all commits from those PRs plus the Flask 2→3 upgrade. If merged to master as-is, it delivers the full Python security + ORM + Flask 3 stack.

⚠️ Merge order: #7719#7723this PR.


Inherited from #7719 and #7723


Incremental fixes added by this PR (Flask 3 migration)

Package Version change CVEs / advisories addressed
flask 2.3.3 → 3.1.3 CVE-2026-27205 (GHSA-68rp-wp8r-4726) — missing Vary: Cookie when session accessed via in/len; CVE-2025-47278 (GHSA-4grg-w6v8-c28g) — incorrect SECRET_KEY_FALLBACKS signing order (patched in 3.1.3)
pyjwt 2.12.0 → 2.13.0 PyJWT 2.13.0 security release — multiple advisories including: GHSA-xgmm-8j9v-c9wx (JWK-as-HMAC algorithm confusion), GHSA-jq35-7prp-9v3f (PyJWK allow-list bypass), GHSA-993g-76c3-p5m4 / CVE-2026-48522 (non-HTTP(S) URI schemes in PyJWKClient), GHSA-w7vc-732c-9m39 / CVE-2026-48525 (DoS via b64=false detached JWS), GHSA-fhv5-28vv-h8m8 (JWK cache cleared on transient fetch errors)
blinker 1.6.2 → 1.9.0 Flask 3 dependency refresh; no named CVE mapped
supervisor 4.1.0 → 4.3.0 Process manager maintenance bump; no named CVE mapped

Code changes (Flask 3 compatibility; security-relevant context)

Change Notes
redash/security.py — CSP dict for flask-talisman Fixes brittle CSP string concatenation for dashboard embed views under Flask 3; reduces risk of misconfigured frame-ancestors silently failing
redash/cli/rq.py — soft-import supervisor_checks Python 3.13 wheel absence; healthcheck uses sys.exit(1) when unavailable (monitoring can detect failure)
tests/test_authentication.py — JWT cache teardown Prevents cross-test JWKS cache pollution

Out of scope (other split PRs)

Area PR
urllib3 2.x + champion SSRF #7721
Frontend / npm CVEs #7720
Debian OS / Docker build-time CVEs #7718

Supersedes

@cubic-dev-ai cubic-dev-ai 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.

1 issue found across 26 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="redash/cli/rq.py">

<violation number="1" location="redash/cli/rq.py:21">
P2: Import-time print() in except ImportError emits to stdout for all CLI commands when supervisor_checks is absent</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread redash/cli/rq.py

SUPERVISOR_CHECKS_AVAILABLE = True
except ImportError as e:
print(f"Warning: supervisor_checks not available: {e}")

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.

P2: Import-time print() in except ImportError emits to stdout for all CLI commands when supervisor_checks is absent

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At redash/cli/rq.py, line 21:

<comment>Import-time print() in except ImportError emits to stdout for all CLI commands when supervisor_checks is absent</comment>

<file context>
@@ -7,8 +7,21 @@
+
+    SUPERVISOR_CHECKS_AVAILABLE = True
+except ImportError as e:
+    print(f"Warning: supervisor_checks not available: {e}")
+    check_runner = None
+    base = None
</file context>

@wtfiwtz

wtfiwtz commented Jul 8, 2026

Copy link
Copy Markdown
Contributor Author

@zachliu @madmuffin1 probably this one next - not the 'rolled up' PR

@cubic-dev-ai cubic-dev-ai 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.

All reported issues were addressed across 2 files (changes from recent commits).

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

Comment thread pyproject.toml Outdated
Nigel Sheridan-Smith and others added 3 commits August 4, 2026 13:36
Land the Werkzeug 3.x framework upgrade while staying on Flask 2.x.
Authlib and related security packages already landed via getredash#7768; boto3/AWS
bumps belong with the urllib3 2.x work.

- werkzeug 2.3.8 → 3.1.6, flask 2.3.2 → 2.3.3
- jinja2 3.1.5 → 3.1.6, itsdangerous 2.1.2 → 2.2.0
- Drop API key lookup from user_loader (request_loader owns it)
- Reset g cache between test requests under TESTING
- Replace removed _app_ctx_stack with current_app in CLI shell

Co-authored-by: Cursor <cursoragent@cursor.com>
Rebased onto the slimmed Werkzeug 3 / Flask 2.3.3 branch (getredash#7719) and
converted dependency pins to the uv pyproject format.

- sqlalchemy 1.3.24 → 1.4.53; flask-sqlalchemy → 3.0.5; flask-migrate → 4.0.7
- ORM/API shims for BaseQuery, paginate, query_order, metrics, ChangeTracking
- Test/infra updates for Flask-SQLAlchemy 3 and pytest 9

Depends on getredash#7719.

Co-authored-by: Cursor <cursoragent@cursor.com>
Stacked on the rebased SQLAlchemy 1.4 branch (getredash#7723 / getredash#7719).

- flask 2.3.3 → 3.1.3; blinker 1.9.0; flask-login/wtf bumps
- Flask 3 CSRF / CLI RQ adaptations
- Keep query_results cleanup subquery fix for Postgres

Depends on getredash#7723 (and transitively getredash#7719).

Co-authored-by: Cursor <cursoragent@cursor.com>
@wtfiwtz

wtfiwtz commented Aug 4, 2026

Copy link
Copy Markdown
Contributor Author

Rebased/stacked onto the refreshed #7723 (itself on slimmed #7719). Flask 3.1.3 + CSRF/CLI adaptations; depends on #7723.

@greptile-apps

greptile-apps Bot commented Aug 4, 2026

Copy link
Copy Markdown

Greptile Summary

This PR upgrades Flask 2.3 → 3.1.3 along with a coordinated set of dependency bumps (Werkzeug 3, Flask-SQLAlchemy 3, Flask-Migrate 4, SQLAlchemy 1.4) and the compatibility code changes required to keep Redash working under the new versions.

  • Flask / SQLAlchemy compatibility: Replaces removed private internals (_ColumnEntity, _mapper_registry, _join_entities, BaseQuery, _app_ctx_stack) with public SQLAlchemy 1.4 and Flask-SQLAlchemy 3.0 APIs; updates before_execute/after_execute event-listener signatures; fixes paginate() to use keyword arguments.
  • Security / CSP: Rewrites csp_allows_embeding to parse the CSP string into a dict and set frame-ancestors: *, replacing fragile substring replacement that could silently fail if the operator customised directive ordering.
  • Test hygiene: Adds tearDown super-calls, moves class-level MonkeyPatch to instance scope, clears JWT key cache between test runs, and soft-imports supervisor_checks to unblock Python 3.13 environments.

Confidence Score: 3/5

The Flask 3 compatibility changes are broadly correct, but the silent removal of nine open-redirect security tests raises an unresolved question about whether backslash-based redirect bypasses are still blocked in Werkzeug 3.

The bulk of the migration work (API removals, paginate signature, CSP dict, event listener signatures) is well-reasoned. The one area that needs more attention before merge is tests/test_authentication.py: deleting nine tests that guard against open-redirect attacks without explaining whether the underlying protection still works leaves an audit gap. The TESTING-gated session-close in execution.py is a minor code smell.

Files Needing Attention: tests/test_authentication.py — confirm open-redirect protection still covers backslash and multi-slash vectors before removing these tests; redash/tasks/queries/execution.py — consider a cleaner fix for the test-isolation problem that does not branch on TESTING in production code.

Security Review

  • Open-redirect regression risk (tests/test_authentication.py): Nine tests covering backslash, multi-slash, javascript:, file:, data:, and scheme-without-netloc open-redirect vectors were deleted. Tracing _is_safe_next_url shows the bare-backslash input \\evil.com passes all checks (backslash→slash normalisation yields /evil.com, a relative path), but Chrome normalises a \\evil.com Location header to //evil.com and follows it as an absolute URL. Whether this is a live vulnerability depends on whether Werkzeug 3's redirect() re-encodes the Location before sending it. The tests should be verified or fixed rather than removed.

Important Files Changed

Filename Overview
tests/test_authentication.py Removes 9 open-redirect security tests (backslash, multi-slash, non-http schemes) without explanation; bare-backslash case (\evil.com) appears to pass _is_safe_next_url and may constitute a live open-redirect in Chrome.
redash/authentication/init.py Removes incorrect API-key lookup from user_loader (correct fix), adds reset_request_g_cache before-request hook to prevent Flask-Login/org-resolution state leaking across test requests.
redash/security.py Rewrites embedding CSP from brittle string concatenation to a parsed dict; _embedding_content_security_policy() is evaluated at decoration time (same as before), which is correct.
redash/tasks/queries/execution.py Gates db.session.close() on not TESTING to work around identity-map conflicts in tests; embeds test-awareness into production code path.
redash/models/init.py Adds if schema is not None guard before sorting, but still writes None to cache when schema fetch fails; future non-refresh calls re-fetch rather than serving stale None, so no permanent regression.
redash/models/base.py Migrates from removed BaseQuery to flask_sqlalchemy.query.Query; adds _entities shim for sqlalchemy-searchable 1.2.0 compatibility with SQLAlchemy 1.4.
redash/metrics/database.py Updates SQLAlchemy 1.4 event listener signatures (adds execution_options param); replaces .froms with .get_final_froms() for SELECT table-name extraction.
redash/utils/query_order.py Removes SQLAlchemy 1.3 private internals (_ColumnEntity, _MapperEntity, _mapper_registry, _join_entities); replaces with public 1.4 APIs (column_descriptions, get_final_froms, _mapper_registries).
redash/cli/rq.py Soft-imports supervisor_checks behind try/except; WorkerHealthcheck is conditionally defined and healthcheck command exits with code 1 when unavailable — safe graceful degradation.
pyproject.toml Bumps Flask 2→3, Werkzeug 2→3, Flask-SQLAlchemy 2→3, Flask-Migrate 2→4, SQLAlchemy 1.3→1.4, and associated transitive dependencies; all pins look consistent with the migration goals.
redash/handlers/base.py Uses keyword args for Flask-SQLAlchemy 3.0 paginate(page=, per_page=, error_out=False); fixes order_results to return unsorted results (preserving ORDER BY) when no valid order and fallback=False.

Sequence Diagram

sequenceDiagram
    participant Browser
    participant Flask3 as Flask 3.1 App
    participant FlaskLogin as Flask-Login
    participant DB as SQLAlchemy 1.4
    participant Redis
    participant Talisman as flask-talisman

    Note over Browser,Talisman: Request lifecycle (Flask 3)

    Browser->>Flask3: GET /api/resource
    Flask3->>FlaskLogin: user_loader(session user_id)
    FlaskLogin->>DB: User.get_by_id_and_org()
    DB-->>FlaskLogin: User
    FlaskLogin-->>Flask3: current_user

    Note over Flask3: before_request: reset_request_g_cache (TESTING only)

    Flask3->>DB: "query.paginate(page=N, per_page=M, error_out=False)"
    DB-->>Flask3: Pagination object

    Flask3->>Talisman: after_request — apply CSP headers
    Note over Talisman: csp_allows_embeding views get frame-ancestors:* dict
    Talisman-->>Browser: Response + CSP headers

    Note over Browser,Redis: Schema fetch path
    Browser->>Flask3: GET /api/data_sources/:id/schema
    Flask3->>Redis: GET schema_key
    Redis-->>Flask3: cached schema (or None)
    alt cache miss / None
        Flask3->>DB: query_runner.get_schema()
        DB-->>Flask3: schema (or None)
        Flask3->>Redis: "SET schema_key json(schema) ex=TTL"
    end
    Flask3-->>Browser: schema JSON
Loading

Comments Outside Diff (1)

  1. tests/test_authentication.py, line 834-906 (link)

    P1 security Open-redirect security tests removed without justification

    Nine tests that verify open-redirect protection are deleted: backslash bypass (%5Cevil.com, /%5Cevil.com), multiple-leading-slash (////evil.com, ///evil.com), and non-http schemes (javascript:, file:, data:, http:///). Tracing _is_safe_next_url shows the bare-backslash case (\evil.com) slips through: the backslash→slash substitution produces /evil.com, which passes all checks. Chrome normalises a \evil.com Location header to //evil.com and follows it as an absolute URL, making this a live open-redirect for Chrome users. The tests should be fixed to reflect actual Flask 3/Werkzeug 3 response.location format rather than deleted.

Reviews (1): Last reviewed commit: "feat: upgrade Flask 2.3.3 → 3.1.3" | Re-trigger Greptile

Comment on lines 189 to +195
# Close DB connection to prevent holding a connection for a long time while the query is executing.
models.db.session.close()
# Skip in testing: closing the session detaches all objects and causes SQLAlchemy identity-map
# conflicts when the same test references factory objects across multiple execute_query calls.
from flask import current_app

if not current_app.config.get("TESTING", False):
models.db.session.close()

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 TESTING flag gates production DB session management

Skipping db.session.close() when TESTING=True works around a test-isolation problem but embeds test-awareness into production code. A cleaner fix is to make the factory objects used in tests independent of the shared SQLAlchemy session (e.g. use db.session.expunge_all() in tearDown, or restructure the test fixtures so objects are not reused across execute_query calls).

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Comment thread redash/models/__init__.py
Comment on lines +219 to +227
if schema is not None:
try:
out_schema = self._sort_schema(schema)
except Exception:
logging.exception("Error sorting schema columns for data_source {}".format(self.id))
out_schema = schema

ttl = int(datetime.timedelta(minutes=settings.SCHEMAS_REFRESH_SCHEDULE, days=7).total_seconds())
redis_connection.set(self._schema_key, json_dumps(out_schema), ex=ttl)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 None schema written to Redis cache when data source unreachable

When query_runner.get_schema() returns None (e.g. data source is temporarily down), out_schema stays None and json_dumps(None) ("null") is written to Redis with the full 7-day TTL. get_cached_schema() then returns None on future non-refresh calls, which re-enters the fetch branch, so there is no permanent cache poisoning. However, each non-refresh call will hit the data source until it returns a non-None schema, potentially causing unexpected load spikes if the scheduled refresher is not running. Consider guarding the redis_connection.set call with if out_schema is not None: to avoid caching a failed fetch result.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant