Skip to content

Commit c23f086

Browse files
TS0713crivetimihai
andauthored
Invalid-transport-type (#572)
* fixes for Bug359 Signed-off-by: Satya <[email protected]> * make flake8 fix Signed-off-by: Satya <[email protected]> --------- Signed-off-by: Satya <[email protected]> Co-authored-by: Mihai Criveti <[email protected]>
1 parent ff8967e commit c23f086

File tree

6 files changed

+67
-46
lines changed

6 files changed

+67
-46
lines changed

mcpgateway/admin.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2299,6 +2299,14 @@ async def admin_add_gateway(request: Request, db: Session = Depends(get_db), use
22992299
# Convert KeyError to ValidationError-like response
23002300
return JSONResponse(content={"message": f"Missing required field: {e}", "success": False}, status_code=422)
23012301

2302+
except ValueError as ex:
2303+
# --- Getting only the custom message from the ValueError ---
2304+
error_ctx = []
2305+
logger.info(f" ALL -> {ex.errors()}")
2306+
for err in ex.errors():
2307+
error_ctx.append(str(err["ctx"]["error"]))
2308+
return JSONResponse(content={"success": False, "message": "; ".join(error_ctx)}, status_code=422)
2309+
23022310
try:
23032311
await gateway_service.register_gateway(db, gateway)
23042312
return JSONResponse(

mcpgateway/config.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -470,7 +470,8 @@ def validate_transport(self) -> None:
470470
... print('error')
471471
error
472472
"""
473-
valid_types = {"http", "ws", "sse", "all"}
473+
# valid_types = {"http", "ws", "sse", "all"}
474+
valid_types = {"sse", "streamablehttp", "all", "http"}
474475
if self.transport_type not in valid_types:
475476
raise ValueError(f"Invalid transport type. Must be one of: {valid_types}")
476477

@@ -489,9 +490,7 @@ def validate_database(self) -> None:
489490
db_dir.mkdir(parents=True)
490491

491492
# Validation patterns for safe display (configurable)
492-
validation_dangerous_html_pattern: str = (
493-
r"<(script|iframe|object|embed|link|meta|base|form|img|svg|video|audio|source|track|area|map|canvas|applet|frame|frameset|html|head|body|style)\b|</*(script|iframe|object|embed|link|meta|base|form|img|svg|video|audio|source|track|area|map|canvas|applet|frame|frameset|html|head|body|style)>"
494-
)
493+
validation_dangerous_html_pattern: str = r"<(script|iframe|object|embed|link|meta|base|form|img|svg|video|audio|source|track|area|map|canvas|applet|frame|frameset|html|head|body|style)\b|</*(script|iframe|object|embed|link|meta|base|form|img|svg|video|audio|source|track|area|map|canvas|applet|frame|frameset|html|head|body|style)>"
495494

496495
validation_dangerous_js_pattern: str = r"javascript:|vbscript:|on\w+\s*=|data:.*script"
497496
validation_allowed_url_schemes: List[str] = ["http://", "https://", "ws://", "wss://"]

mcpgateway/schemas.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
# Standard
2323
import base64
2424
from datetime import datetime, timezone
25+
from enum import Enum
2526
import json
2627
import logging
2728
import re
@@ -1569,6 +1570,14 @@ class PromptInvocation(BaseModelWithConfigDict):
15691570
# --- Gateway Schemas ---
15701571

15711572

1573+
# --- Transport Type ---
1574+
class TransportType(str, Enum):
1575+
SSE = "SSE"
1576+
HTTP = "HTTP"
1577+
STDIO = "STDIO"
1578+
STREAMABLEHTTP = "STREAMABLEHTTP"
1579+
1580+
15721581
class GatewayCreate(BaseModel):
15731582
"""
15741583
Schema for creating a new gateway.
@@ -1678,6 +1687,13 @@ def create_auth_value(cls, v, info):
16781687

16791688
return auth_value
16801689

1690+
@field_validator("transport")
1691+
@classmethod
1692+
def validate_transport(cls, v: str) -> str:
1693+
if v not in [t.value for t in TransportType]:
1694+
raise ValueError(f"Invalid transport type: {v}. Must be one of: {', '.join([t.value for t in TransportType])}")
1695+
return v
1696+
16811697
@staticmethod
16821698
def _process_auth_fields(info: ValidationInfo) -> Optional[Dict[str, Any]]:
16831699
"""

mcpgateway/static/admin.js

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -4218,12 +4218,12 @@ async function handleResourceFormSubmit(e) {
42184218
formData.append("is_inactive_checked", isInactiveCheckedBool);
42194219

42204220
const response = await fetchWithTimeout(
4221-
`${window.ROOT_PATH}/admin/resources`,
4222-
{
4223-
method: "POST",
4224-
body: formData,
4225-
},
4226-
);
4221+
`${window.ROOT_PATH}/admin/resources`,
4222+
{
4223+
method: "POST",
4224+
body: formData,
4225+
},
4226+
);
42274227

42284228
const result = await response.json();
42294229
if (!result.success) {
@@ -4233,20 +4233,20 @@ async function handleResourceFormSubmit(e) {
42334233
? `${window.ROOT_PATH}/admin?include_inactive=true#resources`
42344234
: `${window.ROOT_PATH}/admin#resources`;
42354235
window.location.href = redirectUrl;
4236-
}
4237-
} catch (error) {
4238-
console.error("Error:", error);
4239-
if (status) {
4240-
status.textContent = error.message || "An error occurred!";
4241-
status.classList.add("error-status");
4242-
}
4243-
showErrorMessage(error.message);
4244-
} finally {
4245-
// location.reload();
4246-
if (loading) {
4247-
loading.style.display = "none";
4248-
}
42494236
}
4237+
} catch (error) {
4238+
console.error("Error:", error);
4239+
if (status) {
4240+
status.textContent = error.message || "An error occurred!";
4241+
status.classList.add("error-status");
4242+
}
4243+
showErrorMessage(error.message);
4244+
} finally {
4245+
// location.reload();
4246+
if (loading) {
4247+
loading.style.display = "none";
4248+
}
4249+
}
42504250
}
42514251

42524252
async function handleServerFormSubmit(e) {

mcpgateway/validators.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,9 +52,7 @@ class SecurityValidator:
5252
"""Configurable validation with MCP-compliant limits"""
5353

5454
# Configurable patterns (from settings)
55-
DANGEROUS_HTML_PATTERN = (
56-
settings.validation_dangerous_html_pattern
57-
) # Default: '<(script|iframe|object|embed|link|meta|base|form|img|svg|video|audio|source|track|area|map|canvas|applet|frame|frameset|html|head|body|style)\b|</*(script|iframe|object|embed|link|meta|base|form|img|svg|video|audio|source|track|area|map|canvas|applet|frame|frameset|html|head|body|style)>'
55+
DANGEROUS_HTML_PATTERN = settings.validation_dangerous_html_pattern # Default: '<(script|iframe|object|embed|link|meta|base|form|img|svg|video|audio|source|track|area|map|canvas|applet|frame|frameset|html|head|body|style)\b|</*(script|iframe|object|embed|link|meta|base|form|img|svg|video|audio|source|track|area|map|canvas|applet|frame|frameset|html|head|body|style)>'
5856
DANGEROUS_JS_PATTERN = settings.validation_dangerous_js_pattern # Default: javascript:|vbscript:|on\w+\s*=|data:.*script
5957
ALLOWED_URL_SCHEMES = settings.validation_allowed_url_schemes # Default: ["http://", "https://", "ws://", "wss://"]
6058

tests/security/test_rpc_input_validation.py

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -166,12 +166,12 @@ def test_rpc_xss_injection(self):
166166

167167
results = []
168168
for i, payload in enumerate(self.XSS_PAYLOADS):
169-
logger.debug(f"Testing XSS payload #{i+1}: {payload[:50]}...")
169+
logger.debug(f"Testing XSS payload #{i + 1}: {payload[:50]}...")
170170
try:
171171
RPCRequest(jsonrpc="2.0", method=payload, id=1)
172-
results.append(f"❌ XSS #{i+1} was NOT rejected (security issue!): {payload[:30]}...")
172+
results.append(f"❌ XSS #{i + 1} was NOT rejected (security issue!): {payload[:30]}...")
173173
except ValidationError as e:
174-
results.append(f"✅ XSS #{i+1} correctly rejected: {payload[:30]}... -> {str(e).split(chr(10))[0]}")
174+
results.append(f"✅ XSS #{i + 1} correctly rejected: {payload[:30]}... -> {str(e).split(chr(10))[0]}")
175175

176176
# Print all results
177177
for result in results:
@@ -183,12 +183,12 @@ def test_rpc_sql_injection(self):
183183

184184
results = []
185185
for i, payload in enumerate(self.SQL_INJECTION_PAYLOADS):
186-
logger.debug(f"Testing SQL injection #{i+1}: {payload}")
186+
logger.debug(f"Testing SQL injection #{i + 1}: {payload}")
187187
try:
188188
RPCRequest(jsonrpc="2.0", method=payload, id=1)
189-
results.append(f"❌ SQL injection #{i+1} was NOT rejected (security issue!): {payload}")
189+
results.append(f"❌ SQL injection #{i + 1} was NOT rejected (security issue!): {payload}")
190190
except ValidationError as e:
191-
results.append(f"✅ SQL injection #{i+1} correctly rejected: {payload} -> {str(e).split(chr(10))[0]}")
191+
results.append(f"✅ SQL injection #{i + 1} correctly rejected: {payload} -> {str(e).split(chr(10))[0]}")
192192

193193
# Print all results
194194
for result in results:
@@ -200,12 +200,12 @@ def test_rpc_command_injection(self):
200200

201201
results = []
202202
for i, payload in enumerate(self.COMMAND_INJECTION_PAYLOADS):
203-
logger.debug(f"Testing command injection #{i+1}: {payload}")
203+
logger.debug(f"Testing command injection #{i + 1}: {payload}")
204204
try:
205205
RPCRequest(jsonrpc="2.0", method=payload, id=1)
206-
results.append(f"❌ Command injection #{i+1} was NOT rejected (security issue!): {payload}")
206+
results.append(f"❌ Command injection #{i + 1} was NOT rejected (security issue!): {payload}")
207207
except ValidationError as e:
208-
results.append(f"✅ Command injection #{i+1} correctly rejected: {payload} -> {str(e).split(chr(10))[0]}")
208+
results.append(f"✅ Command injection #{i + 1} correctly rejected: {payload} -> {str(e).split(chr(10))[0]}")
209209

210210
# Print all results
211211
for result in results:
@@ -217,12 +217,12 @@ def test_rpc_path_traversal(self):
217217

218218
results = []
219219
for i, payload in enumerate(self.PATH_TRAVERSAL_PAYLOADS):
220-
logger.debug(f"Testing path traversal #{i+1}: {payload}")
220+
logger.debug(f"Testing path traversal #{i + 1}: {payload}")
221221
try:
222222
RPCRequest(jsonrpc="2.0", method=payload, id=1)
223-
results.append(f"❌ Path traversal #{i+1} was NOT rejected (security issue!): {payload[:30]}...")
223+
results.append(f"❌ Path traversal #{i + 1} was NOT rejected (security issue!): {payload[:30]}...")
224224
except ValidationError as e:
225-
results.append(f"✅ Path traversal #{i+1} correctly rejected: {payload[:30]}... -> {str(e).split(chr(10))[0]}")
225+
results.append(f"✅ Path traversal #{i + 1} correctly rejected: {payload[:30]}... -> {str(e).split(chr(10))[0]}")
226226

227227
# Print all results
228228
for result in results:
@@ -308,12 +308,12 @@ def test_rpc_unicode_attacks(self):
308308

309309
results = []
310310
for i, payload in enumerate(self.UNICODE_PAYLOADS):
311-
logger.debug(f"Testing Unicode attack #{i+1}: {repr(payload[:30])}...")
311+
logger.debug(f"Testing Unicode attack #{i + 1}: {repr(payload[:30])}...")
312312
try:
313313
RPCRequest(jsonrpc="2.0", method=payload, id=1)
314-
results.append(f"❌ Unicode attack #{i+1} was NOT rejected (security issue!)")
314+
results.append(f"❌ Unicode attack #{i + 1} was NOT rejected (security issue!)")
315315
except ValidationError:
316-
results.append(f"✅ Unicode attack #{i+1} correctly rejected")
316+
results.append(f"✅ Unicode attack #{i + 1} correctly rejected")
317317

318318
# Print all results
319319
for result in results:
@@ -325,12 +325,12 @@ def test_rpc_crlf_injection(self):
325325

326326
results = []
327327
for i, payload in enumerate(self.CRLF_INJECTION_PAYLOADS):
328-
logger.debug(f"Testing CRLF injection #{i+1}: {repr(payload[:30])}...")
328+
logger.debug(f"Testing CRLF injection #{i + 1}: {repr(payload[:30])}...")
329329
try:
330330
RPCRequest(jsonrpc="2.0", method=payload, id=1)
331-
results.append(f"❌ CRLF injection #{i+1} was NOT rejected (security issue!)")
331+
results.append(f"❌ CRLF injection #{i + 1} was NOT rejected (security issue!)")
332332
except ValidationError:
333-
results.append(f"✅ CRLF injection #{i+1} correctly rejected")
333+
results.append(f"✅ CRLF injection #{i + 1} correctly rejected")
334334

335335
# Print all results
336336
for result in results:
@@ -470,8 +470,8 @@ def test_rpc_params_validation(self):
470470
deep_params = {"level1": {}}
471471
current = deep_params["level1"]
472472
for i in range(20):
473-
current[f"level{i+2}"] = {}
474-
current = current[f"level{i+2}"]
473+
current[f"level{i + 2}"] = {}
474+
current = current[f"level{i + 2}"]
475475

476476
try:
477477
RPCRequest(jsonrpc="2.0", method="valid_method", params=deep_params)

0 commit comments

Comments
 (0)