Skip to content

fix(mcp): prevent stale g.user from causing user impersonation across tool calls - #38747

Merged
aminghadersohi merged 9 commits into
apache:masterfrom
aminghadersohi:amin/fix-mcp-stale-g-user-impersonation
Mar 30, 2026
Merged

aminghadersohi merged 9 commits into
apache:masterfrom
aminghadersohi:amin/fix-mcp-stale-g-user-impersonation

Conversation

@aminghadersohi

@aminghadersohi aminghadersohi commented Mar 19, 2026 •

Copy link
Copy Markdown
Contributor

User description

SUMMARY

In certain Superset deployments, get_user_from_request() in mcp_service/auth.py blindly returns g.user if already set — without verifying it belongs to the current request. Since mcp_auth_hook pushes Flask app context only (not request context), g.user can persist between tool calls, leading to privilege escalation where one user's MCP actions execute under another user's identity.

Root cause: mcp_auth_hook uses nullcontext() if already in an app context, so g persists. _setup_user_context() sets g.user = user, and the next tool call finds the stale g.user and returns it immediately.

Fix — changed auth priority order in get_user_from_request():

Priority Source Safety
1 JWT auth context (per-request ContextVar from MCP SDK via get_access_token()) Per-async-task safe
2 MCP_DEV_USERNAME config Static dev user
3 g.user fallback For external middleware that sets g.user fresh per request
4 Raise ValueError No auth source

Additional hardening:

  • Clear g.user at the start of each mcp_auth_hook wrapper (belt-and-suspenders)
  • If JWT resolves a username not found in DB → fail closed (raise error, don't fall through to weaker auth)
  • Updated default_user_resolver() to check AccessToken.claims dict first (FastMCP format) and added preferred_username OIDC claim support

Safe for all environments:

Environment Before After
With per-request middleware g.user (fresh, works) No JWT → no DEV_USERNAME → g.user (still fresh)
With JWT auth g.user from PREVIOUS request (BUG) JWT claims → fresh user per request (FIXED)
Dev mode g.user if set, else MCP_DEV_USERNAME No JWT → MCP_DEV_USERNAME (same)

BEFORE/AFTER SCREENSHOTS OR ANIMATED GIF

N/A — backend auth logic change, no UI.

TESTING INSTRUCTIONS

  1. Run new tests: pytest tests/unit_tests/mcp_service/test_auth_user_resolution.py -x -q (20 tests)
  2. Run existing auth tests: pytest tests/unit_tests/mcp_service/test_auth_rbac.py -x -q (15 tests)
  3. Run full MCP test suite: pytest tests/unit_tests/mcp_service/ -x -q (871 tests)

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

MCP Tool Testing Results

Tested against running Superset MCP service (localhost:5008) on 2026-03-24:

Tool Request Result
health_check {} {"status":"healthy","service":"Preset MCP Service","version":"amin/ch101753/bump-fastmcp-3.1.0"}
get_instance_info {} 13 dashboards, 91 charts, 20 datasets, current_user=Amin (Admin)
list_dashboards {"page":1,"page_size":3} Returned 3/13 dashboards (World Bank's Data, Slack Dashboard, FCC New Coder Survey 2018)
list_charts {"page":1,"page_size":3} Returned 3/91 charts (Work Location Preference, Weekly Threads, Weekly Messages)
list_datasets {"page":1,"page_size":3} Returned 3/20 datasets (COVID Vaccines, FCC 2018 Survey, Flights)
get_chart_info {"identifier":4} Returned pie chart "Work Location Preference" with metadata
execute_sql {"database_id":2,"sql":"SELECT 1 as test_col, 'hello' as test_str"} Returned 1 row: {"test_col":1,"test_str":"hello"} in 0.13s
generate_explore_link {"dataset_id":19,"config":{"chart_type":"xy","kind":"bar",...}} Generated explore URL with form_data_key

All tools executed successfully with proper auth context (Admin user).


CodeAnt-AI Description

Prevent stale MCP user data from being reused across tool calls

What Changed

  • MCP requests now resolve the active user from the current JWT first, so one user’s identity is not carried over to the next tool call
  • If JWT-based identity cannot be matched to a Superset user, the request now stops instead of falling back to a weaker identity source
  • User lookup now accepts common login fields such as preferred username and email, and still supports older token formats
  • Stale g.user is cleared before tool setup in app-only flows, while request-scoped middleware identities are preserved
  • Added coverage for JWT priority, fallback behavior, stale user cleanup, and request-context preservation
  • Updated the MCP security notes to match the new auth order

Impact

✅ Fewer cross-user impersonation errors
✅ Safer MCP tool calls
✅ Clearer authentication behavior for Preset and JWT deployments

💡 Usage Guide

Checking Your Pull Request

Every time you make a pull request, our system automatically looks through it. We check for security issues, mistakes in how you're setting up your infrastructure, and common code problems. We do this to make sure your changes are solid and won't cause any trouble later.

Talking to CodeAnt AI

Got a question or need a hand with something in your pull request? You can easily get in touch with CodeAnt AI right here. Just type the following in a comment on your pull request, and replace "Your question here" with whatever you want to ask:

@codeant-ai ask: Your question here

This lets you have a chat with CodeAnt AI about your pull request, making it easier to understand and improve your code.

Example

@codeant-ai ask: Can you suggest a safer alternative to storing this secret?

Preserve Org Learnings with CodeAnt

You can record team preferences so CodeAnt AI applies them in future reviews. Reply directly to the specific CodeAnt AI suggestion (in the same thread) and replace "Your feedback here" with your input:

@codeant-ai: Your feedback here

This helps CodeAnt AI learn and adapt to your team's coding style and standards.

Example

@codeant-ai: Do not flag unused imports.

Retrigger review

Ask CodeAnt AI to review the PR again, by typing:

@codeant-ai: review

Check Your Repository Health

To analyze the health of your code repository, visit our dashboard at https://app.codeant.ai. This tool helps you identify potential issues and areas for improvement in your codebase, ensuring your repository maintains high standards of code health.

@bito-code-review

bito-code-review Bot commented Mar 19, 2026 •

Copy link
Copy Markdown
Contributor

Code Review Agent Run #97f378

Actionable Suggestions - 0
Additional Suggestions - 1
  • tests/unit_tests/mcp_service/test_auth_user_resolution.py - 1
    • Incorrect Type Hints in Test Helper · Line 43-43
      Update the type hints for `claims` and `**kwargs` in `_make_access_token` to `dict[str, Any] | None` and `**kwargs: Any`, and add `from typing import Any` at the top of the file.
Review Details
  • Files reviewed - 3 · Commit Range: a93a61d..a93a61d
    • superset/mcp_service/auth.py
    • superset/mcp_service/mcp_config.py
    • tests/unit_tests/mcp_service/test_auth_user_resolution.py
  • Files skipped - 1
    • superset/mcp_service/SECURITY.md - Reason: Filter setting
  • Tools
    • Whispers (Secret Scanner) - ✔︎ Successful
    • Detect-secrets (Secret Scanner) - ✔︎ Successful
    • MyPy (Static Code Analysis) - ✔︎ Successful
    • Astral Ruff (Static Code Analysis) - ✔︎ Successful

Bito Usage Guide

Commands

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

  • /review - 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

@dosubot dosubot Bot added authentication Related to authentication change:backend Requires changing the backend labels Mar 19, 2026
@codeant-ai-for-open-source codeant-ai-for-open-source Bot added the size:L This PR changes 100-499 lines, ignoring generated files label Mar 19, 2026
@codeant-ai-for-open-source

Copy link
Copy Markdown
Contributor

Sequence Diagram

This PR changes MCP authentication to resolve the user from per-request JWT context before any shared Flask state. It also clears stale g.user at the start of each tool call and fails closed when a JWT user does not exist in the database.

sequenceDiagram
    participant Tool as MCP Tool Call
    participant Hook as Auth Hook
    participant Auth as User Resolver
    participant DB as Superset Database

    Tool->>Hook: Invoke tool
    Hook->>Hook: Clear stale g.user
    Hook->>Auth: Resolve current user
    alt JWT context available
        Auth->>DB: Load user from JWT claims
        DB-->>Auth: User found or missing
        alt User missing
            Auth-->>Hook: Raise authentication error
        else User found
            Auth-->>Hook: Return JWT user
        end
    else No JWT context
        Auth-->>Hook: Fallback to MCP_DEV_USERNAME then g.user
    end
    Hook->>Tool: Execute as resolved user
Loading

Generated by CodeAnt AI

@codecov

codecov Bot commented Mar 19, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 4.61538% with 62 lines in your changes missing coverage. Please review.
✅ Project coverage is 64.47%. Comparing base (15bab22) to head (1e41f09).
⚠️ Report is 1 commits behind head on master.

Files with missing lines Patch % Lines
superset/mcp_service/auth.py 5.26% 54 Missing ⚠️
superset/mcp_service/mcp_config.py 0.00% 8 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master   #38747      +/-   ##
==========================================
- Coverage   64.48%   64.47%   -0.02%     
==========================================
  Files        2536     2536              
  Lines      130862   130895      +33     
  Branches    30358    30367       +9     
==========================================
+ Hits        84383    84389       +6     
- Misses      45016    45043      +27     
  Partials     1463     1463              
Flag Coverage Δ
hive 40.13% <4.61%> (-0.02%) ⬇️
mysql 61.03% <4.61%> (-0.04%) ⬇️
postgres 61.12% <4.61%> (-0.04%) ⬇️
presto 40.14% <4.61%> (-0.02%) ⬇️
python 62.71% <4.61%> (-0.04%) ⬇️
sqlite 60.74% <4.61%> (-0.04%) ⬇️
unit 100.00% <ø> (ø)

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

☔ View full report in Codecov by Sentry.
📢 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.

Comment thread superset/mcp_service/auth.py Outdated
Comment thread superset/mcp_service/auth.py
Comment thread superset/mcp_service/auth.py Outdated
Comment thread superset/mcp_service/mcp_config.py Outdated
Comment thread tests/unit_tests/mcp_service/test_auth_user_resolution.py Outdated
@netlify

netlify Bot commented Mar 19, 2026 •

Copy link
Copy Markdown

✅ Deploy Preview for superset-docs-preview ready!

Name Link
🔨 Latest commit 17b0314
🔍 Latest deploy log https://app.netlify.com/projects/superset-docs-preview/deploys/69c25ef84c11900008ad834b
😎 Deploy Preview https://deploy-preview-38747--superset-docs-preview.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@bito-code-review

bito-code-review Bot commented Mar 19, 2026 •

Copy link
Copy Markdown
Contributor

Code Review Agent Run #83b5ff

Actionable Suggestions - 0
Review Details
  • Files reviewed - 3 · Commit Range: a93a61d..508869d
    • superset/mcp_service/auth.py
    • superset/mcp_service/mcp_config.py
    • tests/unit_tests/mcp_service/test_auth_user_resolution.py
  • Files skipped - 0
  • Tools
    • Whispers (Secret Scanner) - ✔︎ Successful
    • Detect-secrets (Secret Scanner) - ✔︎ Successful
    • MyPy (Static Code Analysis) - ✔︎ Successful
    • Astral Ruff (Static Code Analysis) - ✔︎ Successful

Bito Usage Guide

Commands

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

  • /review - 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

@aminghadersohi
aminghadersohi force-pushed the amin/fix-mcp-stale-g-user-impersonation branch from 508869d to 17b0314 Compare March 24, 2026 09:52
@codeant-ai-for-open-source codeant-ai-for-open-source Bot added size:XL This PR changes 500-999 lines, ignoring generated files and removed size:L This PR changes 100-499 lines, ignoring generated files labels Mar 24, 2026
Comment thread tests/unit_tests/mcp_service/test_auth_user_resolution.py Outdated
Comment thread tests/unit_tests/mcp_service/test_auth_user_resolution.py Outdated
Comment thread tests/unit_tests/mcp_service/test_auth_user_resolution.py Outdated
@bito-code-review

bito-code-review Bot commented Mar 24, 2026 •

Copy link
Copy Markdown
Contributor

Code Review Agent Run #bdda03

Actionable Suggestions - 0
Additional Suggestions - 1
  • tests/unit_tests/mcp_service/test_auth_user_resolution.py - 1
    • Incomplete Test Coverage for Legacy Payload Fallback · Line 361-420
      The default_user_resolver function includes a legacy fallback to access_token.payload for username extraction when the claims dict is empty, but the new tests only cover claims dict and other legacy attributes like subject and client_id. While the current tests validate the primary logic, consider adding test cases for payload fallback scenarios to ensure complete branch coverage of this legacy compatibility path.
Review Details
  • Files reviewed - 3 · Commit Range: dee6708..4c10be6
    • superset/mcp_service/auth.py
    • superset/mcp_service/mcp_config.py
    • tests/unit_tests/mcp_service/test_auth_user_resolution.py
  • Files skipped - 1
    • superset/mcp_service/SECURITY.md - Reason: Filter setting
  • Tools
    • Whispers (Secret Scanner) - ✔︎ Successful
    • Detect-secrets (Secret Scanner) - ✔︎ Successful
    • MyPy (Static Code Analysis) - ✔︎ Successful
    • Astral Ruff (Static Code Analysis) - ✔︎ Successful

Bito Usage Guide

Commands

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

  • /review - 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

@eschutho eschutho reopened this Mar 28, 2026
@aminghadersohi
aminghadersohi force-pushed the amin/fix-mcp-stale-g-user-impersonation branch from 4c10be6 to a18140c Compare March 29, 2026 12:19
@codeant-ai-for-open-source codeant-ai-for-open-source Bot added size:XL This PR changes 500-999 lines, ignoring generated files and removed size:XL This PR changes 500-999 lines, ignoring generated files labels Mar 29, 2026
Comment thread tests/unit_tests/mcp_service/test_auth_user_resolution.py
@bito-code-review

bito-code-review Bot commented Mar 29, 2026 •

Copy link
Copy Markdown
Contributor

Code Review Agent Run #2c5cc6

Actionable Suggestions - 0
Review Details
  • Files reviewed - 3 · Commit Range: f1e1cbf..add0305
    • superset/mcp_service/auth.py
    • superset/mcp_service/mcp_config.py
    • tests/unit_tests/mcp_service/test_auth_user_resolution.py
  • Files skipped - 1
    • superset/mcp_service/SECURITY.md - Reason: Filter setting
  • Tools
    • Whispers (Secret Scanner) - ✔︎ Successful
    • Detect-secrets (Secret Scanner) - ✔︎ Successful
    • MyPy (Static Code Analysis) - ✔︎ Successful
    • Astral Ruff (Static Code Analysis) - ✔︎ Successful

Bito Usage Guide

Commands

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

  • /review - 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

@bito-code-review

bito-code-review Bot commented Mar 29, 2026 •

Copy link
Copy Markdown
Contributor

Code Review Agent Run #c56f00

Actionable Suggestions - 0
Review Details
  • Files reviewed - 1 · Commit Range: add0305..3f7168c
    • tests/unit_tests/mcp_service/conftest.py
  • Files skipped - 0
  • Tools
    • Whispers (Secret Scanner) - ✔︎ Successful
    • Detect-secrets (Secret Scanner) - ✔︎ Successful
    • MyPy (Static Code Analysis) - ✔︎ Successful
    • Astral Ruff (Static Code Analysis) - ✔︎ Successful

Bito Usage Guide

Commands

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

  • /review - 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

@aminghadersohi
aminghadersohi force-pushed the amin/fix-mcp-stale-g-user-impersonation branch from 3f7168c to c8e3bae Compare March 30, 2026 14:41
@codeant-ai-for-open-source codeant-ai-for-open-source Bot added size:XL This PR changes 500-999 lines, ignoring generated files and removed size:XL This PR changes 500-999 lines, ignoring generated files labels Mar 30, 2026
… tool calls

In non-Preset (open source) environments, g.user could persist between
MCP tool calls, causing one user's actions to execute under another
user's identity. This changes the auth priority to use the MCP SDK's
per-request JWT context (ContextVar-based) first, falling back to
MCP_DEV_USERNAME, then g.user as a last resort for middleware compat.

Also clears g.user at the start of each mcp_auth_hook call and fails
closed when JWT resolves a username not found in the DB.
- Fail closed when JWT present but no username extractable (don't fall
  through to weaker auth sources)
- Add email fallback lookup when resolver returns an email address
- Only clear g.user when no request context is active (preserve
  middleware-set identity in request-scoped contexts)
- Reorder claims priority: prefer preferred_username/username/email
  over opaque sub (common OIDC pattern)
- Use asyncio.run() instead of get_event_loop().run_until_complete()
- Add tests for email fallback and request-context preservation
…avior

Replace hard-mocked return_value with side_effect callbacks that assert
g.user state before returning the mock user. This ensures:

- Stale user tests fail if g.pop("user") is removed from mcp_auth_hook
- Preserved user test fails if g.user is incorrectly cleared in request context

Addresses review feedback from codeant-ai bot.
…10 compat

The g.pop("user", None) call was inside sync_wrapper/async_wrapper which
gets copied via types.FunctionType(wrapper.__code__, merged_globals).
The merged_globals override causes Python 3.10's LOAD_GLOBAL bytecode to
not properly resolve flask.g.pop, making the stale g.user tests fail in
CI (Python 3.10) while passing locally (Python 3.12).

Moving the clearing logic into _setup_user_context() ensures it executes
as a regular function call via closure (LOAD_DEREF) rather than depending
on the merged globals dictionary.
Extract API key resolution logic from get_user_from_request() into a
dedicated helper function to reduce cyclomatic complexity below the
ruff C901 threshold of 10.
@aminghadersohi
aminghadersohi force-pushed the amin/fix-mcp-stale-g-user-impersonation branch from c8e3bae to 92c6d78 Compare March 30, 2026 15:03
The test_existing_g_user_takes_precedence test was written for the old
priority order where g.user was checked first. Update it to match the
new priority (JWT > API key > dev username > g.user fallback).
Codebase uses @pytest.fixture without parentheses. Local ruff adds
them but CI ruff removes them. Match CI/codebase convention.
@bito-code-review

bito-code-review Bot commented Mar 30, 2026 •

Copy link
Copy Markdown
Contributor

Code Review Agent Run #d202fc

Actionable Suggestions - 0
Additional Suggestions - 4
  • tests/unit_tests/mcp_service/test_auth_api_key.py - 1
    • Test assertion should verify g.user fallback behavior · Line 158-158
      The test assertion checks the username instead of verifying that the exact g.user object is returned as fallback, which doesn't fully validate the behavior. Also, using '_disable_api_keys' prevents API key auth from being attempted, so the test doesn't cover the fallback scenario when API keys are enabled but fail. Change to assert identity and enable API keys for accurate testing.
      Code suggestion
       @@ -149,1 +149,1 @@
      -@pytest.mark.usefixtures("_disable_api_keys")
      +@pytest.mark.usefixtures("_enable_api_keys")
       @@ -156,3 +156,3 @@
      -        result = get_user_from_request()
      - 
      -    assert result.username == "api_key_user"
      +        result = get_user_from_request()
      + 
      +    assert result is mock_user
  • tests/unit_tests/mcp_service/test_auth_user_resolution.py - 2
    • Incorrect test docstring for email claim · Line 382-382
      The test docstring incorrectly states that 'email' falls back when 'sub' is absent, but the function prioritizes claims as preferred_username > username > email > sub. It should clarify that 'email' is used when the higher-priority claims are absent.
      Code suggestion
       @@ -382,1 +382,1 @@
      -    """Falls back to 'email' claim when 'sub' is absent."""
      +    """Falls back to 'email' claim when 'preferred_username' and 'username' are absent."""
    • Incorrect test docstring for username claim · Line 388-388
      The test docstring vaguely says 'falls back to 'username' claim' without clarifying the priority. It should note that 'username' is used when 'preferred_username' is absent, as per the function's logic.
      Code suggestion
       @@ -388,1 +388,1 @@
      -    """Falls back to 'username' claim."""
      +    """Falls back to 'username' claim when 'preferred_username' is absent."""
  • superset/mcp_service/auth.py - 1
    • Redundant Import · Line 444-444
      The import of has_request_context inside the _setup_user_context function is redundant, as it's already imported at the top of the file. This violates Python import conventions and should be removed for cleaner code.
      Code suggestion
       @@ -443,2 +443,1 @@
      -    # a request context is active (external middleware set it).
      -    from flask import has_request_context
      +    # a request context is active (external middleware set it).
Review Details
  • Files reviewed - 4 · Commit Range: 541dcc8..e83ec56
    • superset/mcp_service/auth.py
    • superset/mcp_service/mcp_config.py
    • tests/unit_tests/mcp_service/test_auth_api_key.py
    • tests/unit_tests/mcp_service/test_auth_user_resolution.py
  • Files skipped - 1
    • superset/mcp_service/SECURITY.md - Reason: Filter setting
  • Tools
    • Whispers (Secret Scanner) - ✔︎ Successful
    • Detect-secrets (Secret Scanner) - ✔︎ Successful
    • MyPy (Static Code Analysis) - ✔︎ Successful
    • Astral Ruff (Static Code Analysis) - ✔︎ Successful

Bito Usage Guide

Commands

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

  • /review - 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

…mpat

CI's test framework may implicitly provide a request context even when
only app.app_context() is used, causing has_request_context() to return
True and skip the g.user clearing. Explicitly mock it to False to ensure
consistent behavior across local and CI environments.
_setup_user_context does a local 'from flask import has_request_context'
which creates a fresh binding that bypasses the module-level patch.
Patch flask.has_request_context directly to ensure the mock is picked up.
@bito-code-review

bito-code-review Bot commented Mar 30, 2026 •

Copy link
Copy Markdown
Contributor

Code Review Agent Run #b76a56

Actionable Suggestions - 0
Review Details
  • Files reviewed - 1 · Commit Range: e83ec56..1e41f09
    • tests/unit_tests/mcp_service/test_auth_user_resolution.py
  • Files skipped - 0
  • Tools
    • Whispers (Secret Scanner) - ✔︎ Successful
    • Detect-secrets (Secret Scanner) - ✔︎ Successful
    • MyPy (Static Code Analysis) - ✔︎ Successful
    • Astral Ruff (Static Code Analysis) - ✔︎ Successful

Bito Usage Guide

Commands

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

  • /review - 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

@aminghadersohi
aminghadersohi requested a review from kgabryje March 30, 2026 18:02
@aminghadersohi
aminghadersohi merged commit 38fdfb4 into apache:master Mar 30, 2026
65 checks passed
qfcwell pushed a commit to qfcwell/superset that referenced this pull request May 12, 2026
rad-pat added a commit to PlaidCloud/incubator-superset that referenced this pull request Aug 20, 2026
Every MCP tool call failed with "No authenticated user found" on every
tenant. Nothing bridged the verified JWT to a Superset user: FastMCP
produced an AccessToken with claims and nothing read it, while
mcp_config.default_user_resolver sat unreferenced. get_user_from_request()
knew only a pre-set g.user -- populated solely by Preset's hosted
WorkspaceContextMiddleware, which we do not run -- and MCP_DEV_USERNAME,
which is unset in production. Same bug as apache#41610.

Backport _resolve_user_from_jwt_context and the claims-aware
default_user_resolver from apache#38747, absent from 6.1.0. The
cherry-pick does not apply (master has since drifted to an MCPUser union
via apache#39604/apache#41003), so this ports the function as introduced. JWT now
takes priority over MCP_DEV_USERNAME and g.user, so a stale g.user from a
previous tool call can never win, and both no-identity and unknown-user
cases fail closed rather than falling through to a weaker source. The
"no auth source" message is now generic, with the configuration
diagnostics kept to the server log.

Upstream's resolver prefers preferred_username, which matches no user row
here: oauth_user_info sets the Superset username from the Keycloak display
name and auth_user_oauth keys identity on email. Add plaid_user_resolver,
wired via MCP_USER_RESOLVER, returning the email claim. It deliberately has
no client_id fallback -- that identifies the MCP client, not a person.

Routing authentication through the email lookup makes sc-24028 live: it was
case-sensitive and used .first() with no ordering, so an ambiguous match
resolved to an arbitrary user with that user's permissions. Compare
case-insensitively and resolve deterministically via _resolve_single_user,
mirroring plaid.security (active row, then exact case, then lowest id).

Also make the OAuth proxy's accepted client redirect patterns configurable.
They were hardcoded to loopback, and FastMCP raises when patterns are set
and none match, so claude.ai web could never complete OAuth on any tenant --
the Keycloak realm never sees the client's redirect URI, only
<base_url>/auth/callback. Default now covers loopback plus the Claude web
callback, overridable with MCP_OAUTH_ALLOWED_CLIENT_REDIRECT_URIS.

plaid/mcp_oauth.py is additionally converted to ruff's quote style; CI
scopes pre-commit to the files a PR touches, so its pre-existing drift
would otherwise fail this change.

Verified on pw-bug-fixes from claude.ai web: OAuth completed and
list_dashboards (RBAC-gated on Dashboard) returned data.

sc-25291
sc-24028

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Mk6GoSg5qpMXWpFBQ5vtL4
rad-pat added a commit to PlaidCloud/incubator-superset that referenced this pull request Aug 20, 2026
Every MCP tool call failed with "No authenticated user found" on every
tenant. Nothing bridged the verified JWT to a Superset user: FastMCP
produced an AccessToken with claims and nothing read it, while
mcp_config.default_user_resolver sat unreferenced. get_user_from_request()
knew only a pre-set g.user -- populated solely by Preset's hosted
WorkspaceContextMiddleware, which we do not run -- and MCP_DEV_USERNAME,
which is unset in production. Same bug as apache#41610.

Backport _resolve_user_from_jwt_context and the claims-aware
default_user_resolver from apache#38747, absent from 6.1.0. The
cherry-pick does not apply (master has since drifted to an MCPUser union
via apache#39604/apache#41003), so this ports the function as introduced. JWT now
takes priority over MCP_DEV_USERNAME and g.user, so a stale g.user from a
previous tool call can never win, and both no-identity and unknown-user
cases fail closed rather than falling through to a weaker source. The
"no auth source" message is now generic, with the configuration
diagnostics kept to the server log.

Upstream's resolver prefers preferred_username, which matches no user row
here: oauth_user_info sets the Superset username from the Keycloak display
name and auth_user_oauth keys identity on email. Add plaid_user_resolver,
wired via MCP_USER_RESOLVER, returning the email claim. It deliberately has
no client_id fallback -- that identifies the MCP client, not a person.

Routing authentication through the email lookup makes sc-24028 live: it was
case-sensitive and used .first() with no ordering, so an ambiguous match
resolved to an arbitrary user with that user's permissions. Compare
case-insensitively and resolve deterministically via _resolve_single_user,
mirroring plaid.security (active row, then exact case, then lowest id).

Also make the OAuth proxy's accepted client redirect patterns configurable.
They were hardcoded to loopback, and FastMCP raises when patterns are set
and none match, so claude.ai web could never complete OAuth on any tenant --
the Keycloak realm never sees the client's redirect URI, only
<base_url>/auth/callback. Default now covers loopback plus the Claude web
callback, overridable with MCP_OAUTH_ALLOWED_CLIENT_REDIRECT_URIS.

plaid/mcp_oauth.py is additionally converted to ruff's quote style; CI
scopes pre-commit to the files a PR touches, so its pre-existing drift
would otherwise fail this change.

Verified on pw-bug-fixes from claude.ai web: OAuth completed and
list_dashboards (RBAC-gated on Dashboard) returned data.

sc-25291
sc-24028

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Mk6GoSg5qpMXWpFBQ5vtL4
rad-pat added a commit to PlaidCloud/incubator-superset that referenced this pull request Aug 20, 2026
Every MCP tool call failed with "No authenticated user found" on every
tenant. Nothing bridged the verified JWT to a Superset user: FastMCP
produced an AccessToken with claims and nothing read it, while
mcp_config.default_user_resolver sat unreferenced. get_user_from_request()
knew only a pre-set g.user -- populated solely by Preset's hosted
WorkspaceContextMiddleware, which we do not run -- and MCP_DEV_USERNAME,
which is unset in production. Same bug as apache#41610.

Backport _resolve_user_from_jwt_context and the claims-aware
default_user_resolver from apache#38747, absent from 6.1.0. The
cherry-pick does not apply (master has since drifted to an MCPUser union
via apache#39604/apache#41003), so this ports the function as introduced. JWT now
takes priority over MCP_DEV_USERNAME and g.user, so a stale g.user from a
previous tool call can never win, and both no-identity and unknown-user
cases fail closed rather than falling through to a weaker source. The
"no auth source" message is now generic, with the configuration
diagnostics kept to the server log.

Upstream's resolver prefers preferred_username, which matches no user row
here: oauth_user_info sets the Superset username from the Keycloak display
name and auth_user_oauth keys identity on email. Add plaid_user_resolver,
wired via MCP_USER_RESOLVER, returning the email claim. It deliberately has
no client_id fallback -- that identifies the MCP client, not a person.

Routing authentication through the email lookup makes sc-24028 live: it was
case-sensitive and used .first() with no ordering, so an ambiguous match
resolved to an arbitrary user with that user's permissions. Compare
case-insensitively and resolve deterministically via _resolve_single_user,
mirroring plaid.security (active row, then exact case, then lowest id).

Also make the OAuth proxy's accepted client redirect patterns configurable.
They were hardcoded to loopback, and FastMCP raises when patterns are set
and none match, so claude.ai web could never complete OAuth on any tenant --
the Keycloak realm never sees the client's redirect URI, only
<base_url>/auth/callback. Default now covers loopback plus the Claude web
callback, overridable with MCP_OAUTH_ALLOWED_CLIENT_REDIRECT_URIS.

plaid/mcp_oauth.py is additionally converted to ruff's quote style; CI
scopes pre-commit to the files a PR touches, so its pre-existing drift
would otherwise fail this change.

Verified on pw-bug-fixes from claude.ai web: OAuth completed and
list_dashboards (RBAC-gated on Dashboard) returned data.

sc-25291
sc-24028

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Mk6GoSg5qpMXWpFBQ5vtL4
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

authentication Related to authentication change:backend Requires changing the backend size/XL size:XL This PR changes 500-999 lines, ignoring generated files

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants