The Model Context Protocol Python SDK is the reference library
for MCP servers and clients. Two prefix checks in that tree
accept values that walk past the prefix. A Host of
wild.example:9000.evil satisfies
wild.example:*. A resource URL of
/api/../admin satisfies a token scoped to
/api. The Host patch is
PR 3465.
Their bot closed it pending assignment on
#3463.
| Document | HB-AR-2026.4 / public source review, HTTP allowlist and resource indicators |
|---|---|
| What this is | A research note and an opinion. The MCP project did not hire Hackerbane for this. |
| System | modelcontextprotocol/python-sdk, the MCP Python SDK |
| Scope |
TransportSecurityMiddleware Host/Origin
:* matching, and
check_resource_allowed path prefix matching
|
| Review commit | main @ 08a3bc8 |
| Upstream | PR 3465 (closed by their issue-assignment bot) · #3463 · #3464 |
| Date | 7 September 2026 |
| Author | Barney Chambers, Hackerbane |
If you have not used this SDK: it is the Python library for
MCP. Servers can sit on Streamable HTTP or SSE. A middleware
can check Host and Origin so a page on another site cannot
point your browser at the server. The check has a wildcard
form, host:*, meaning any port on that host.
On main at commit 08a3bc8 that
wildcard is a prefix:
if host.startswith(base_host + ":"):
return True
wild.example:9000.evil starts with
wild.example:. The tests already allow
wild.example:9000. They do not reject a suffix
after the port. I opened a patch that requires the suffix
to be digits. It is
PR 3465.
This repo only keeps outside PRs open when a maintainer
assigns the linked issue. The bot closed 3465. The issue is
#3463.
The branch is still there.
A second helper, check_resource_allowed, decides
whether a requested resource URL sits under a configured one
for OAuth resource indicators. It already rejects
/api123 against /api. It accepts
/api/../admin. That is
#3464.
I could not open a second PR from this account after the first.
The patch is on
Oskii/python-sdk branch
fix/resource-indicator-dot-segments.
The MCP project did not hire this review. The name on the cover names the project I read.
| # | ID | Action | Status on 7 Sep 2026 |
|---|---|---|---|
| 1 | MED-01 | Require the suffix after a :* allowlist entry to be digits. |
Patch in PR 3465. Bot-closed. Issue #3463 open. |
| 2 | MED-02 | Normalise resource indicator paths before the prefix check. | Issue #3464 open. Patch on the fork. |
This is not a full audit of the SDK. I split the tree and
read auth, transports, stdio, tools, and path helpers. The
two findings I can prove with a unit test on current
main are the ones above. I did not treat
documented trust boundaries as bugs. I did not review every
language SDK in the MCP org.
| In scope |
src/mcp/server/transport_security.py:
Host and Origin :* matching.
src/mcp/shared/auth_utils.py:
check_resource_allowed.
Tests in
tests/server/test_transport_security.py
and
tests/shared/test_auth_utils.py.
|
|---|---|
| Out of scope | Other MCP language SDKs. Hosted MCP product surfaces. Stdio spawn of a command the caller passed on purpose. FastMCP deploy defaults when auth is left off. |
| Method |
Read the code. Compare the existing allowlist and
path-boundary tests with the matcher. Add cases that fail
on 08a3bc8 and pass with the patch.
|
| Retest |
uv run pytest tests/server/test_transport_security.py tests/shared/test_auth_utils.py -q
|
| ID | Severity | Title | Status |
|---|---|---|---|
| MED-01 | Medium | :* Host/Origin allowlist accepts a suffix that is not a port |
PR 3465, bot-closed |
| MED-02 | Medium | Resource indicator prefix treats /api/../admin as under /api |
Issue #3464 open |
I called both Medium, not High, because each check is
optional or sits behind an operator-configured prefix. MED-01
only fires when DNS rebinding protection is on and the
allowlist uses :*. MED-02 only fires when someone
relies on check_resource_allowed for hierarchical
scope. Both are still the check you think you have.
src/mcp/server/transport_security.py · _validate_host · _validate_origin
When protection is on, Host must be in
allowed_hosts and Origin, if present, must be
in allowed_origins. An entry that ends in
:* is documented as any port on that base.
The implementation accepts any string that starts with
base:.
On 08a3bc8, with
allowed_hosts=["wild.example:*"], Host
wild.example:9000.evil is accepted. Origin
http://wild.example:9000.evil is accepted for
http://wild.example:*.
The patch requires the suffix after base: to
be digits. wild.example:9000 still passes.
An empty port does not.
src/mcp/shared/auth_utils.py · check_resource_allowed
The helper compares scheme, host, and port, then asks
whether the requested path starts with the configured
path after adding a trailing slash. That is how
/api123 is rejected against
/api. Dot-segments are left in place, so
/api/../admin/ still starts with
/api/.
After one percent-decode and
posixpath.normpath,
/api/../admin is /admin and
the prefix fails. /api/foo/../v1 against
/api still matches, because it stays under
/api.
| When | What |
|---|---|
| 7 Sep 2026 |
Review commit 08a3bc8. MED-01 and MED-02
confirmed. Tests fail on that commit and pass with the
patches.
|
| 7 Sep 2026 | Opened #3463 and PR 3465 for MED-01. Their contributing rules auto-close outside PRs until a maintainer assigns the issue. 3465 is closed for that reason. The branch was not rewritten. |
| 7 Sep 2026 |
Opened
#3464
for MED-02. A second PR from this account was
refused after 3465. The patch is
Oskii/python-sdk
fix/resource-indicator-dot-segments.
|
To retest, run the command in section 2 against the two
branches, or apply the same helper changes on
08a3bc8.
Read this before you treat anything above as advice.
This file is a public research note. The Model Context Protocol project did not hire Hackerbane. There is no services agreement, statement of work, or client relationship behind it. The MCP name on the cover names the project I read. It is not a partnership badge.
What you have is an opinion about the files and commits named in section 2. It is not a certificate. It is not an endorsement of MCP, of the Python SDK, or of the patches. It is not a claim that the rest of the SDK is safe, or that it is broken.
I am not telling you to merge, deploy, buy, or sell anything. This is not investment advice, legal advice, or a security guarantee. If you ship software, you own that risk. If you run an MCP server, you own that risk.
Hackerbane and I accept no liability for how anyone uses this note, for bugs it does not name, or for loss that follows from relying on it. Findings can be wrong, incomplete, or already stale the day after the review commit. Code added after that commit was not reviewed. A retest covers only the checks it names.
You may share this file. Sharing it does not create a contract and does not make the reader a client.
This note is provided as-is, as available, with all faults. To the maximum extent the law allows, Hackerbane disclaims every warranty, express or implied, including merchantability, fitness for a particular purpose, title, and non-infringement. No promise that this text is accurate, complete, or free of error, or that any error will be fixed.
Nobody may rely on this note as a professional engagement. It is not financial, tax, legal, or regulatory advice. No third party is a beneficiary of it.
| Critical | Someone could steal funds or hurt people with what I looked at. |
|---|---|
| High | The check you think you have does not work in the common case. |
| Medium | A real miss, less common in the output I care about here. |
| Low | Real, but limited, or it needs an unlikely setup. |
| Informational | On the record. Not scored as a bug. |
The coloured bars are labels. The word High or Medium is what counts.