Skip to content

fix(security): skip session-invalidation check on anonymous requests - #44528

Open
dpgaspar wants to merge 2 commits into
apache:masterfrom
preset-io:fix/session-invalidation-anonymous-requests
Open

dpgaspar wants to merge 2 commits into
apache:masterfrom
preset-io:fix/session-invalidation-anonymous-requests

Conversation

@dpgaspar

Copy link
Copy Markdown
Member

SUMMARY

SupersetAppInitializer registers enforce_session_validity as an unconditional
before_request hook, and the hook resolves current_user on every request —
anonymous page loads, static assets, token-authenticated API calls included.

Two problems with that:

  1. It is wasteful. current_user is a LocalProxy whose resolution can hit
    the metadata database. Nothing about an anonymous request needs it: this hook
    can only ever invalidate a session login, because the per-user epoch is
    compared against _login_at, which is stamped into the session at login time.
    A request with no session login has nothing for the hook to invalidate.

  2. It assumes current_user never raises. That holds for cookie sessions,
    where an unauthenticated request resolves to the anonymous user. It does not
    hold for deployments that install a Flask-Login request_loader — raising
    from the loader is a common way to trigger a custom login redirect. Under such
    a deployment every anonymous request produces a logger.warning with a full
    traceback from the fail-open handler. In one deployment that is hundreds of MB
    of log noise per day, all of it describing ordinary unauthenticated traffic.

The fix is two small changes to enforce_session_validity:

  • Return early, before touching current_user, when the request carries no
    session login. A "remember me" cookie still counts as one, since Flask-Login
    restores that login while resolving current_user — i.e. after this hook runs
    — so the cookie's presence keeps the check enabled.
  • Split the except: a JWT-based loader that finds no usable token raises
    JWTExtendedException, which now logs at debug instead of warning. An
    unauthenticated request is not a failure of this check. Any other error keeps
    the existing fail-open warning with exc_info.

Behaviour for the case the mechanism exists to serve is unchanged: a browser
session for a disabled user carries _user_id, so it is still checked and still
forced out on its next request.

This follows the same reasoning as the existing health-probe early return
(#43786), generalized from one endpoint to "no session login".

TESTING INSTRUCTIONS

pytest tests/unit_tests/security/test_session_invalidation.py
pytest tests/integration_tests/security/session_invalidation_tests.py

New unit tests cover:

  • an anonymous request never resolves current_user (asserts __bool__ is not
    called, mirroring the existing health-probe test);
  • a request with a session login does reach the epoch lookup;
  • a "remember me" cookie alone also reaches the epoch lookup;
  • a request loader that raises NoAuthorizationError is logged at debug, not
    warning, and the request is allowed.

Manual: install a Flask-Login request_loader that raises
NoAuthorizationError for credential-less requests, hit any anonymous route,
and confirm the logs stay clean; then log in, disable the account, and confirm
the next request is still forced out.

ADDITIONAL INFORMATION

  • Has associated issue:
  • Required feature flags:
  • Changes UI
  • Includes DB Migration (follow approval process in SIP-59)
    • Migration is atomic, supports rollback & is backwards-compatible
    • Confirm DB migration upgrade and downgrade tested
    • Runtime estimates and downtime expectations provided
  • Introduces new feature or API
  • Removes existing feature or API

The ``enforce_session_validity`` ``before_request`` hook resolved
``current_user`` on every request, including anonymous page loads, static
assets and token-authenticated API calls.

That is wasteful — ``current_user`` is a LocalProxy whose resolution can
query the metadata database — and it is fragile: deployments are free to
install a Flask-Login ``request_loader``, and a loader that *raises* for
credential-less requests (a common way to trigger a custom login redirect)
instead of returning the anonymous user turns every anonymous request into
a warning with a full traceback. Upstream assumed ``current_user`` always
resolves to an anonymous user, which only holds for cookie sessions.

The hook can only ever invalidate a *session* login: the per-user epoch is
compared against ``_login_at``, stamped into the session at login. So return
before touching ``current_user`` when the request carries no session login,
and log a JWT-based loader finding no token at debug rather than warning —
an unauthenticated request is not a failure of this check.

A "remember me" cookie still counts as a session login: Flask-Login restores
it while resolving ``current_user``, after this hook runs.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@bito-code-review

bito-code-review Bot commented Sep 22, 2026 •

Copy link
Copy Markdown
Contributor

Code Review Agent Run #8a61c7

Actionable Suggestions - 0
Review Details
  • Files reviewed - 2 · Commit Range: 2e18146..2e18146
    • superset/security/session_invalidation.py
    • tests/unit_tests/security/test_session_invalidation.py
  • Files skipped - 0
  • Tools
    • MyPy (Static Code Analysis) - ✔︎ Successful
    • Astral Ruff (Static Code Analysis) - ✔︎ Successful
    • Whispers (Secret Scanner) - ✔︎ Successful
    • Detect-secrets (Secret Scanner) - ✔︎ Successful

Bito Usage Guide

Commands

Type the following command in the pull request comment and save the comment.

  • /review - Manually triggers an incremental AI Review.

  • /review full - Manually triggers a full AI Review.

  • /pause - Pauses automatic reviews on this pull request.

  • /resume - Resumes automatic reviews.

  • /resolve - Marks all Bito-posted review comments as resolved.

  • /abort - Cancels all in-progress reviews.

Refer to the documentation for additional commands.

Configuration

This repository uses Superset You can customize the agent settings here or contact your Bito workspace admin at evan@preset.io.

Documentation & Help

AI Code Review powered by Bito Logo

@codecov

codecov Bot commented Sep 22, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 82.35294% with 3 lines in your changes missing coverage. Please review.
✅ Project coverage is 66.19%. Comparing base (9612437) to head (228cd93).

Files with missing lines Patch % Lines
superset/security/session_invalidation.py 82.35% 2 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master   #44528      +/-   ##
==========================================
- Coverage   66.22%   66.19%   -0.03%     
==========================================
  Files        2949     2949              
  Lines      177381   177384       +3     
  Branches    41107    41102       -5     
==========================================
- Hits       117465   117414      -51     
- Misses      57413    57464      +51     
- Partials     2503     2506       +3     
Flag Coverage Δ
hive 36.93% <70.58%> (+<0.01%) ⬆️
mysql 56.15% <82.35%> (+<0.01%) ⬆️
postgres 56.15% <82.35%> (-0.01%) ⬇️
presto 38.87% <70.58%> (+<0.01%) ⬆️
python 56.47% <82.35%> (-0.01%) ⬇️
sqlite 55.87% <82.35%> (+<0.01%) ⬆️

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

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 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.

@rusackas rusackas left a comment

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.

Good catch on the anonymous-request hot path and the remember-cookie edge case, the early-return logic there checks out.

One thing before this merges though: except JWTExtendedException is wider than the "no usable token" case this is meant to quiet down. That base class also covers RevokedTokenError, JWTDecodeError (a tampered/malformed token), CSRFError, UserClaimsVerificationError, and WrongTokenError, all of which are more interesting than ordinary anonymous traffic and arguably still belong at warning. Since the PR's own tests and the manual repro only ever exercise NoAuthorizationError, narrowing the except to that (plus UserLookupError if you want to cover the "no role" case too, same as #44213) would quiet the real noise without losing visibility into revoked/tampered tokens hitting this path.

Separately, the one failing check (check_pot_drift) is unrelated to this change, it's flagging a string from something else merged to master since this branch was cut. A rebase should clear it.

This branch has not been deployed

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants