fix(security): guard is_guest_user against NoAuthorizationError on unauthenticated error paths - #43826
fix(security): guard is_guest_user against NoAuthorizationError on unauthenticated error paths#43826eschutho wants to merge 1 commit into
Conversation
…19741) When Flask's global HTTPException handler (`show_http_exception`) processes a benign early-lifecycle error such as a 405 MethodNotAllowed, it calls `json_error_response` → `sanitize_superset_errors` → `is_sanitization_required` → `security_manager.is_guest_user()`. With no `user` argument, `is_guest_user` resolves the current user, which forces evaluation of flask_login's `current_user` LocalProxy. On a request with no JWT, no guest token, and not a public/MCP-OAuth path, the app's request loader calls `verify_jwt_in_request()` and lets it raise `NoAuthorizationError`. Because this happens inside the error handler for the original exception, the second exception escapes unhandled and is captured by Sentry, turning a benign 405 into a crash for embedded-enabled deployments on any unauthenticated request that trips an HTTPException path before auth runs. Wrap the current-user resolution in `is_guest_user` in `try/except NoAuthorizationError: return False`. A request carrying no JWT/guest token definitionally cannot be an embedded guest viewer, so `False` is the semantically correct answer rather than merely crash avoidance. This protects all `is_guest_user` call sites without changing behavior for any already-authenticated caller. Adds a regression test. Fixes SUPERSET-PYTHON-15J6 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Code Review Agent Run #8128Actionable Suggestions - 0Additional Suggestions - 1
Review Details
Bito Usage GuideCommands Type the following command in the pull request comment and save the comment.
Refer to the documentation for additional commands. Configuration This repository uses Documentation & Help |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #43826 +/- ##
=======================================
Coverage 79.41% 79.41%
=======================================
Files 2894 2894
Lines 167820 167824 +4
Branches 38863 38863
=======================================
+ Hits 133269 133273 +4
Misses 32051 32051
Partials 2500 2500
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
SUMMARY
Fixes SUPERSET-PYTHON-15J6 —
NoAuthorizationError: Missing JWT in cookies or headers, 28,783 events since firstSeen 2026-08-21, still firing daily.The Sentry-captured exception is not the original problem — it is a secondary exception raised while Flask is already handling an unrelated, benign exception (in the sampled events, a 405
MethodNotAllowedfrom a request hitting the wrong HTTP method on a route, before any view/auth code runs). The same crash can happen for any early-in-request-lifecycleHTTPException.Root-cause chain
MethodNotAllowed(405) during routing, before any view/auth code runs.@app.errorhandler(HTTPException) def show_http_exception(superset/views/error_handling.py) catches it and callsjson_error_response(...).json_error_responsecallssanitize_superset_errors()(superset/utils/error_sanitization.py).is_sanitization_required()→security_manager.is_guest_user().is_guest_user()with nouserargument callsget_current_user()(superset/tasks/utils.py), whoseg.useraccess forces resolution of flask_login'scurrent_userLocalProxy.verify_jwt_in_request()and lets it raise (by design — a global flask-jwt-extended handler is meant to turn that into a 401 for a real unauthenticated view request).show_http_exception, the error handler for the original 405. This second exception is not caught by anything and propagates out unhandled — captured by Sentry, and turning a benign 405 into an actual crash/500 for any embedded-enabled deployment, for any unauthenticated request that trips anHTTPExceptionpath before auth ever runs.The fix
Wrap the current-user resolution inside
SupersetSecurityManager.is_guest_user()intry/except NoAuthorizationError: return False.A request that carries no JWT and no guest token definitionally cannot be an embedded guest viewer, so returning
Falseis the semantically correct answer, not merely crash avoidance.is_guest_useris the shared choke point behind many call sites across the codebase; only the error-sanitization path is normally reachable before auth runs, and this fix protects all of them without changing behavior for any already-authenticated caller.NoAuthorizationErroris imported fromflask_jwt_extended.exceptions(the top-level package does not re-export it). The fix lives entirely insuperset/security/manager.py;error_sanitization.py,error_handling.py, and the private request-loader are intentionally untouched — their behavior is correct for real view requests.TESTING INSTRUCTIONS
Added
tests/unit_tests/security/manager_test.py::test_is_guest_user_no_jwt_returns_false_without_raising: withEMBEDDED_SUPERSETenabled and the current-user resolution raisingNoAuthorizationError,is_guest_user()must returnFalserather than propagate.Verified the test fails before the fix (raises
NoAuthorizationError) and passes after:pre-commit(mypy, ruff, ruff-format, pylint) clean on the changed files.Tradeoffs
None as a failure-mode change. This makes
is_guest_user()never raise where it previously could sometimes crash the request, which is strictly more correct:Falseis the semantically correct return value (such a request cannot be an embedded guest), so no legitimate guest is misclassified.NoAuthorizationErroris never hit and behavior is byte-for-byte unchanged.ADDITIONAL INFORMATION
Fixes SUPERSET-PYTHON-15J6)