Skip to content

Fix substring matches in WWW-Authenticate parsing#3041

Open
Whning0513 wants to merge 3 commits into
modelcontextprotocol:mainfrom
Whning0513:fix-www-auth-substring-match-3009
Open

Fix substring matches in WWW-Authenticate parsing#3041
Whning0513 wants to merge 3 commits into
modelcontextprotocol:mainfrom
Whning0513:fix-www-auth-substring-match-3009

Conversation

@Whning0513

Copy link
Copy Markdown

Fixes #3009.

extract_field_from_www_auth currently matches field_name as a substring of another auth-param name, so fields like scope can be shadowed by error_scope and resource_metadata can be shadowed by x_resource_metadata.

This change anchors the auth-param name to the start of the header or a parameter separator, and adds regressions for both the shadowing and false-positive cases.

Tested with:

  • PYTHONPATH=src:src/mcp-types python -m pytest -q tests/client/test_auth.py -k "extract_field_from_www_auth_valid_cases or extract_field_from_www_auth_invalid_cases"
  • PYTHONPATH=src:src/mcp-types python -m pytest -q tests/client/test_scope_bug_1630.py
  • PYTHONPATH=src:src/mcp-types python -m pytest -q tests/client/test_auth.py

Copilot AI review requested due to automatic review settings July 1, 2026 13:03

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

This PR fixes a correctness issue in extract_field_from_www_auth where an auth-param name could be matched as a substring of a different param name (e.g., scope incorrectly matching inside error_scope), and adds regression coverage to prevent reintroducing the bug.

Changes:

  • Anchor auth-param name matching to the start of the header or a parameter separator to avoid substring shadowing.
  • Escape field_name in the regex to ensure literal matching.
  • Add regression test cases covering both “shadowing” (real param present but a decoy exists) and “false positive” (only decoy param exists).

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.

File Description
src/mcp/client/auth/utils.py Updates extract_field_from_www_auth regex to require a real auth-param name boundary and escape the field name.
tests/client/test_auth.py Adds regression cases ensuring substring-shadowing and substring-only params no longer match.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

1 issue found across 2 files

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

Fix all with cubic | Re-trigger cubic

Comment thread src/mcp/client/auth/utils.py Outdated

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

1 issue found across 2 files (changes from recent commits).

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

Fix all with cubic | Re-trigger cubic

Comment thread src/mcp/client/auth/utils.py Outdated

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

2 issues found across 2 files (changes from recent commits).

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="src/mcp/client/auth/utils.py">

<violation number="1" location="src/mcp/client/auth/utils.py:50">
P2: When a `WWW-Authenticate` header contains more than one Bearer challenge, the parser currently keeps overwriting the collected auth params and ends up using the last Bearer challenge. That can make scope/resource metadata extraction come from a different challenge than intended. The overwrite happens because every `Bearer ...` segment reinitializes `auth_params`; stopping at the first Bearer challenge (or only initializing once) would keep behavior consistent with the function contract.</violation>

<violation number="2" location="src/mcp/client/auth/utils.py:56">
P2: Bearer params after a valid `auth-param` that uses optional whitespace before `=` can be skipped, so fields later in the same challenge may return `None`. The break condition is classifying `scope = "..."`-style segments as a new challenge; consider excluding segments where the remainder starts with `=`.</violation>
</file>

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

Fix all with cubic | Re-trigger cubic

Comment on lines +56 to +57
if separator and "=" not in scheme:
break

@cubic-dev-ai cubic-dev-ai Bot Jul 2, 2026

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: Bearer params after a valid auth-param that uses optional whitespace before = can be skipped, so fields later in the same challenge may return None. The break condition is classifying scope = "..."-style segments as a new challenge; consider excluding segments where the remainder starts with =.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/mcp/client/auth/utils.py, line 56:

<comment>Bearer params after a valid `auth-param` that uses optional whitespace before `=` can be skipped, so fields later in the same challenge may return `None`. The break condition is classifying `scope = "..."`-style segments as a new challenge; consider excluding segments where the remainder starts with `=`.</comment>

<file context>
@@ -16,6 +16,52 @@
+            continue
+
+        if collecting:
+            if separator and "=" not in scheme:
+                break
+            auth_params.append(segment)
</file context>
Suggested change
if separator and "=" not in scheme:
break
if separator and "=" not in scheme and not remainder.lstrip().startswith("="):
break
Fix with cubic

Comment on lines +50 to +53
if scheme.lower() == "bearer" and separator:
collecting = True
auth_params = [remainder.strip()]
continue

@cubic-dev-ai cubic-dev-ai Bot Jul 2, 2026

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: When a WWW-Authenticate header contains more than one Bearer challenge, the parser currently keeps overwriting the collected auth params and ends up using the last Bearer challenge. That can make scope/resource metadata extraction come from a different challenge than intended. The overwrite happens because every Bearer ... segment reinitializes auth_params; stopping at the first Bearer challenge (or only initializing once) would keep behavior consistent with the function contract.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/mcp/client/auth/utils.py, line 50:

<comment>When a `WWW-Authenticate` header contains more than one Bearer challenge, the parser currently keeps overwriting the collected auth params and ends up using the last Bearer challenge. That can make scope/resource metadata extraction come from a different challenge than intended. The overwrite happens because every `Bearer ...` segment reinitializes `auth_params`; stopping at the first Bearer challenge (or only initializing once) would keep behavior consistent with the function contract.</comment>

<file context>
@@ -16,6 +16,52 @@
+
+    for segment in segments:
+        scheme, separator, remainder = segment.partition(" ")
+        if scheme.lower() == "bearer" and separator:
+            collecting = True
+            auth_params = [remainder.strip()]
</file context>
Suggested change
if scheme.lower() == "bearer" and separator:
collecting = True
auth_params = [remainder.strip()]
continue
if scheme.lower() == "bearer" and separator:
if collecting:
break
collecting = True
auth_params = [remainder.strip()]
continue
Fix with cubic

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.

WWW-Authenticate parsing matches a field name as a substring of another auth-param

2 participants