Spaces:
Running
Running
Fix MCP API key bypass vulnerability
Browse filesCRITICAL SECURITY FIX:
- Add strict MCP request path detection (/mcp/, /gradio_api/mcp)
- Enforce mandatory API key for all MCP endpoint requests
- Prevent bypass via --transport sse-only parameter
- Add detailed request debugging and path analysis
- Improve error messages with clear configuration examples
Changes:
- Detect MCP requests by URL path analysis
- Block Space API key usage for MCP endpoints
- Add comprehensive request logging for debugging
- Ensure MCP clients cannot access without proper API key
This prevents the security issue where MCP clients could
access the service without API keys using sse-only transport.
- app.py +42 -4
- test_no_key.py +43 -0
app.py
CHANGED
|
@@ -17,6 +17,7 @@ def get_api_client():
|
|
| 17 |
# Try to get API key from multiple sources
|
| 18 |
api_key = None
|
| 19 |
user_agent = ""
|
|
|
|
| 20 |
|
| 21 |
# 1. Try from request headers (for MCP clients)
|
| 22 |
try:
|
|
@@ -25,7 +26,10 @@ def get_api_client():
|
|
| 25 |
headers = dict(request.headers)
|
| 26 |
api_key = get_api_key_from_headers(headers)
|
| 27 |
user_agent = headers.get('user-agent', '')
|
|
|
|
|
|
|
| 28 |
print(f"π Request headers found - User-Agent: {user_agent}")
|
|
|
|
| 29 |
print(
|
| 30 |
f"π API key from headers: {'Found' if api_key else 'Not found'}")
|
| 31 |
except Exception as e:
|
|
@@ -39,6 +43,13 @@ def get_api_client():
|
|
| 39 |
|
| 40 |
# 3. Determine if this is a web browser request or MCP client request
|
| 41 |
is_web_request = False
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 42 |
if user_agent:
|
| 43 |
user_agent_lower = user_agent.lower()
|
| 44 |
# Web browsers typically have 'mozilla' in user agent
|
|
@@ -52,15 +63,42 @@ def get_api_client():
|
|
| 52 |
is_web_request = False
|
| 53 |
print("π No User-Agent found - assuming MCP client request")
|
| 54 |
|
| 55 |
-
# 4.
|
| 56 |
-
if
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 57 |
print("π‘ Using API key from Space environment variable (web demo)")
|
| 58 |
return A1DAPIClient(space_api_key)
|
| 59 |
|
| 60 |
-
#
|
| 61 |
if not api_key:
|
| 62 |
error_msg = (
|
| 63 |
-
"π API key is required
|
| 64 |
"Please provide API_KEY in request headers.\n"
|
| 65 |
"Get your API key at https://a1d.ai\n\n"
|
| 66 |
"Configuration example:\n"
|
|
|
|
| 17 |
# Try to get API key from multiple sources
|
| 18 |
api_key = None
|
| 19 |
user_agent = ""
|
| 20 |
+
request_path = ""
|
| 21 |
|
| 22 |
# 1. Try from request headers (for MCP clients)
|
| 23 |
try:
|
|
|
|
| 26 |
headers = dict(request.headers)
|
| 27 |
api_key = get_api_key_from_headers(headers)
|
| 28 |
user_agent = headers.get('user-agent', '')
|
| 29 |
+
request_path = getattr(request, 'url', {}).path if hasattr(
|
| 30 |
+
request, 'url') else ""
|
| 31 |
print(f"π Request headers found - User-Agent: {user_agent}")
|
| 32 |
+
print(f"π Request path: {request_path}")
|
| 33 |
print(
|
| 34 |
f"π API key from headers: {'Found' if api_key else 'Not found'}")
|
| 35 |
except Exception as e:
|
|
|
|
| 43 |
|
| 44 |
# 3. Determine if this is a web browser request or MCP client request
|
| 45 |
is_web_request = False
|
| 46 |
+
is_mcp_request = False
|
| 47 |
+
|
| 48 |
+
# Check if this is an MCP request
|
| 49 |
+
if request_path and ('/mcp/' in request_path or '/gradio_api/mcp' in request_path):
|
| 50 |
+
is_mcp_request = True
|
| 51 |
+
print("π Detected MCP API request")
|
| 52 |
+
|
| 53 |
if user_agent:
|
| 54 |
user_agent_lower = user_agent.lower()
|
| 55 |
# Web browsers typically have 'mozilla' in user agent
|
|
|
|
| 63 |
is_web_request = False
|
| 64 |
print("π No User-Agent found - assuming MCP client request")
|
| 65 |
|
| 66 |
+
# 4. STRICT RULE: MCP requests MUST have API key
|
| 67 |
+
if is_mcp_request and not api_key:
|
| 68 |
+
error_msg = (
|
| 69 |
+
"π API key is REQUIRED for MCP requests!\n\n"
|
| 70 |
+
"This is an MCP API endpoint. You must provide your API key.\n"
|
| 71 |
+
"Get your API key at https://a1d.ai\n\n"
|
| 72 |
+
"Configuration example:\n"
|
| 73 |
+
'{\n'
|
| 74 |
+
' "mcpServers": {\n'
|
| 75 |
+
' "a1d": {\n'
|
| 76 |
+
' "command": "npx",\n'
|
| 77 |
+
' "args": [\n'
|
| 78 |
+
' "mcp-remote@latest",\n'
|
| 79 |
+
' "https://aigchacker-a1d-mcp-server.hf.space/gradio_api/mcp/sse",\n'
|
| 80 |
+
' "--header",\n'
|
| 81 |
+
' "API_KEY:${MCP_API_KEY}"\n'
|
| 82 |
+
' ],\n'
|
| 83 |
+
' "env": {\n'
|
| 84 |
+
' "MCP_API_KEY": "your_a1d_api_key_here"\n'
|
| 85 |
+
' }\n'
|
| 86 |
+
' }\n'
|
| 87 |
+
' }\n'
|
| 88 |
+
'}'
|
| 89 |
+
)
|
| 90 |
+
print(f"β MCP API key validation failed: {error_msg}")
|
| 91 |
+
raise ValueError(error_msg)
|
| 92 |
+
|
| 93 |
+
# 5. Use Space API key ONLY for web browser requests on Hugging Face Space
|
| 94 |
+
if not api_key and is_space and space_api_key and is_web_request and not is_mcp_request:
|
| 95 |
print("π‘ Using API key from Space environment variable (web demo)")
|
| 96 |
return A1DAPIClient(space_api_key)
|
| 97 |
|
| 98 |
+
# 6. For all other cases, user API key is mandatory
|
| 99 |
if not api_key:
|
| 100 |
error_msg = (
|
| 101 |
+
"π API key is required!\n\n"
|
| 102 |
"Please provide API_KEY in request headers.\n"
|
| 103 |
"Get your API key at https://a1d.ai\n\n"
|
| 104 |
"Configuration example:\n"
|
test_no_key.py
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""
|
| 3 |
+
Test script to verify API key enforcement without environment variables
|
| 4 |
+
"""
|
| 5 |
+
|
| 6 |
+
import os
|
| 7 |
+
import sys
|
| 8 |
+
|
| 9 |
+
# Remove any existing API key environment variable
|
| 10 |
+
if 'A1D_API_KEY' in os.environ:
|
| 11 |
+
del os.environ['A1D_API_KEY']
|
| 12 |
+
|
| 13 |
+
# Import after removing environment variable
|
| 14 |
+
from app import remove_bg_wrapper
|
| 15 |
+
|
| 16 |
+
def test_no_api_key():
|
| 17 |
+
"""Test that API key is required when not provided"""
|
| 18 |
+
try:
|
| 19 |
+
result = remove_bg_wrapper("https://example.com/test.jpg")
|
| 20 |
+
print(f"β FAILED: Function should have failed but returned: {result}")
|
| 21 |
+
return False
|
| 22 |
+
except Exception as e:
|
| 23 |
+
print(f"β
SUCCESS: Function correctly failed with error: {str(e)}")
|
| 24 |
+
return True
|
| 25 |
+
|
| 26 |
+
if __name__ == "__main__":
|
| 27 |
+
print("π§ͺ Testing API key enforcement...")
|
| 28 |
+
print("=" * 50)
|
| 29 |
+
|
| 30 |
+
# Check environment
|
| 31 |
+
print(f"A1D_API_KEY in environment: {'A1D_API_KEY' in os.environ}")
|
| 32 |
+
print(f"SPACE_ID in environment: {'SPACE_ID' in os.environ}")
|
| 33 |
+
|
| 34 |
+
# Run test
|
| 35 |
+
success = test_no_api_key()
|
| 36 |
+
|
| 37 |
+
print("=" * 50)
|
| 38 |
+
if success:
|
| 39 |
+
print("β
Test PASSED: API key enforcement is working")
|
| 40 |
+
sys.exit(0)
|
| 41 |
+
else:
|
| 42 |
+
print("β Test FAILED: API key enforcement is NOT working")
|
| 43 |
+
sys.exit(1)
|