fix(security): validate Bearer token value in MCP endpoints, not just presence

Previously any Bearer token (even invalid) passed the HTTP middleware and
got a session with access to tools/list and resources/list. Now the
OAuthRequiredMiddleware validates tokens against master key, API keys,
and OAuth JWT before allowing initialize.

Also fixes:
- WordPress Advanced env prefix: WORDPRESS_ → WORDPRESS_ADVANCED_ in docs
- Bump version to 3.0.1

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
airano
2026-02-18 16:24:04 +03:30
parent 4a45178d2d
commit 85379ec36c
6 changed files with 51 additions and 5 deletions

View File

@@ -108,7 +108,7 @@ Add more sites with `SITE2`, `SITE3`, etc. See [full configuration guide](https:
|--------|-------|------------| |--------|-------|------------|
| WordPress | 67 | `WORDPRESS_` | | WordPress | 67 | `WORDPRESS_` |
| WooCommerce | 28 | `WOOCOMMERCE_` | | WooCommerce | 28 | `WOOCOMMERCE_` |
| WordPress Advanced | 22 | `WORDPRESS_` | | WordPress Advanced | 22 | `WORDPRESS_ADVANCED_` |
| Gitea | 56 | `GITEA_` | | Gitea | 56 | `GITEA_` |
| n8n | 56 | `N8N_` | | n8n | 56 | `N8N_` |
| Supabase | 70 | `SUPABASE_` | | Supabase | 70 | `SUPABASE_` |

View File

@@ -1995,7 +1995,7 @@ def _get_project_version() -> str:
return line.split("=")[1].strip().strip('"').strip("'") return line.split("=")[1].strip().strip('"').strip("'")
except Exception: except Exception:
pass pass
return "3.0.0" return "3.0.1"
def get_about_info() -> dict: def get_about_info() -> dict:

View File

@@ -138,7 +138,7 @@
<!-- Footer --> <!-- Footer -->
<p class="text-center text-gray-500 text-sm mt-6"> <p class="text-center text-gray-500 text-sm mt-6">
MCP Hub v{{ version|default('3.0.0') }} MCP Hub v{{ version|default('3.0.1') }}
</p> </p>
</div> </div>
</body> </body>

View File

@@ -319,7 +319,7 @@ MCP Hub supports **Open Dynamic Client Registration** (RFC 7591). ChatGPT can au
|--------|-------|------------| |--------|-------|------------|
| WordPress | 67 | `WORDPRESS_` | | WordPress | 67 | `WORDPRESS_` |
| WooCommerce | 28 | `WOOCOMMERCE_` | | WooCommerce | 28 | `WOOCOMMERCE_` |
| WordPress Advanced | 22 | `WORDPRESS_` (same sites, advanced ops) | | WordPress Advanced | 22 | `WORDPRESS_ADVANCED_` |
| Gitea | 56 | `GITEA_` | | Gitea | 56 | `GITEA_` |
| n8n | 56 | `N8N_` | | n8n | 56 | `N8N_` |
| Supabase | 70 | `SUPABASE_` | | Supabase | 70 | `SUPABASE_` |

View File

@@ -1,6 +1,6 @@
[project] [project]
name = "mcphub-server" name = "mcphub-server"
version = "3.0.0" version = "3.0.1"
description = "AI-native management hub for WordPress, WooCommerce, and self-hosted services via Model Context Protocol (MCP)" description = "AI-native management hub for WordPress, WooCommerce, and self-hosted services via Model Context Protocol (MCP)"
authors = [ authors = [
{name = "MCP Hub", email = "contact@mcphub.dev"} {name = "MCP Hub", email = "contact@mcphub.dev"}

View File

@@ -4053,8 +4053,54 @@ def create_multi_endpoint_app(transport: str = "streamable-http"):
}, },
) )
# Validate the token value (not just presence)
token = auth_header.removeprefix("Bearer ").strip()
if token and not self._is_valid_token(token):
base_url = get_oauth_base_url(request)
resource_metadata_url = f"{base_url}/.well-known/oauth-protected-resource"
logger.warning(f"MCP OAuth: Invalid token for {path}")
return Response(
content='{"error": "invalid_token", "error_description": "Bearer token is invalid"}',
status_code=401,
media_type="application/json",
headers={
"WWW-Authenticate": f'Bearer resource_metadata="{resource_metadata_url}", error="invalid_token"',
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Headers": "Authorization, Content-Type",
"Access-Control-Expose-Headers": "WWW-Authenticate",
},
)
return await call_next(request) return await call_next(request)
@staticmethod
def _is_valid_token(token: str) -> bool:
"""Check if a Bearer token is recognized by any auth backend."""
# 1. Master API key
if auth_manager.validate_master_key(token):
return True
# 2. Per-project API key (any valid key, skip project/scope check)
if token.startswith("cmp_"):
key_hash = api_key_manager._hash_key(token)
for key in api_key_manager.keys.values():
if key.key_hash == key_hash and key.is_valid():
return True
return False
# 3. OAuth JWT token
try:
from core.oauth import get_token_manager
token_manager = get_token_manager()
if token_manager.validate_access_token(token):
return True
except Exception:
pass
return False
# Create MCP instances # Create MCP instances
system_mcp = create_system_mcp() # Phase X.3 - System endpoint system_mcp = create_system_mcp() # Phase X.3 - System endpoint
wp_mcp = create_wordpress_mcp() wp_mcp = create_wordpress_mcp()