Skip to content

[Security]: Add input validation for main API endpoints (depends on #339 /admin API validation) #340

@crivetimihai

Description

@crivetimihai

Title: Implement input validation for non-admin API endpoints

Depends on: #339

Description:
Following the implementation of input validation for admin endpoints (#339), this ticket extends the same validation framework to all remaining API endpoints. This ensures consistent data integrity and proper handling of user input across the entire API surface.

API Endpoints Requiring Validation:

1. Server Management APIs (/servers/*)

# app/main.py - Server APIs
- [ ] POST /servers
- [ ] PUT /servers/{server_id}
- [ ] POST /servers/{server_id}/message

Fields requiring validation:

  • name: Server names displayed in UI
  • description: Server descriptions
  • icon: Image URLs
  • associatedTools, associatedResources, associatedPrompts: Reference lists

2. Tool Management APIs (/tools/*)

# app/main.py - Tool APIs
- [ ] POST /tools
- [ ] PUT /tools/{tool_id}

Fields requiring validation:

  • Same as admin endpoints - reuse SecureToolCreate and SecureToolUpdate models

3. Resource Management APIs (/resources/*)

# app/main.py - Resource APIs
- [ ] POST /resources
- [ ] PUT /resources/{uri:path}

Fields requiring validation:

  • Same as admin endpoints - reuse SecureResourceCreate and SecureResourceUpdate models

4. Prompt Management APIs (/prompts/*)

# app/main.py - Prompt APIs
- [ ] POST /prompts
- [ ] POST /prompts/{name} (with args)
- [ ] PUT /prompts/{name}

Fields requiring validation:

  • Same as admin endpoints - reuse SecurePromptCreate and SecurePromptUpdate models
  • Additional validation for args parameter in POST /prompts/{name}

5. Gateway Management APIs (/gateways/*)

# app/main.py - Gateway APIs
- [ ] POST /gateways
- [ ] PUT /gateways/{gateway_id}

Fields requiring validation:

  • Same as admin endpoints - reuse SecureGatewayCreate and SecureGatewayUpdate models

6. Root Management APIs (/roots/*)

# app/main.py - Root APIs
- [ ] POST /roots

Fields requiring validation:

  • uri: Root URI identifier
  • name: Root name for display

7. RPC Endpoint

# app/main.py - RPC endpoint
- [ ] POST /rpc

Fields requiring validation:

  • Reuse SecureRPCRequest model from Issue 1
  • Additional validation for method-specific parameters

8. Protocol APIs (/protocol/*)

# app/main.py - Protocol APIs
- [ ] POST /protocol/initialize
- [ ] POST /protocol/notifications
- [ ] POST /protocol/completion/complete
- [ ] POST /protocol/sampling/createMessage

Fields requiring validation:

  • Protocol-specific request bodies
  • Message content that may be displayed

9. Utility Endpoints

# app/main.py - Utility routes
- [ ] POST /message (SSE messages)
- [ ] POST /logging/setLevel

Fields requiring validation:

  • Message content for SSE
  • Log level enumeration

Implementation Tasks:

A. Apply Existing Validators (from Issue 1):

# Reuse validators from app/core/validators.py
from app.core.validators import SecurityValidator

# Apply to non-admin schemas
from app.schemas import (
    ToolCreate, ToolUpdate,
    ResourceCreate, ResourceUpdate,
    PromptCreate, PromptUpdate,
    GatewayCreate, GatewayUpdate,
    ServerCreate, ServerUpdate
)

B. Create Additional Models:

# app/schemas/roots.py
class SecureRootCreate(BaseModel):
    model_config = ConfigDict(str_strip_whitespace=True)
    
    uri: str
    name: str
    
    @field_validator('uri')
    @classmethod
    def validate_uri(cls, v: str) -> str:
        """Validate root URI format"""
        return SecurityValidator.validate_identifier(v, "Root URI")
    
    @field_validator('name')
    @classmethod
    def validate_name(cls, v: str) -> str:
        """Validate root name"""
        return SecurityValidator.validate_name(v, "Root name")

# app/schemas/protocol.py
class SecureInitializeRequest(BaseModel):
    model_config = ConfigDict(str_strip_whitespace=True)
    
    protocol_version: str
    capabilities: Dict[str, Any] = {}
    client_info: Dict[str, Any] = {}
    
    @field_validator('protocol_version')
    @classmethod
    def validate_version(cls, v: str) -> str:
        """Validate protocol version format"""
        if not re.match(r'^\d+\.\d+\.\d+$', v):
            raise ValueError("Invalid protocol version format")
        return v
    
    @field_validator('capabilities', 'client_info')
    @classmethod
    def validate_json_fields(cls, v: Dict[str, Any]) -> Dict[str, Any]:
        """Validate JSON structure depth"""
        SecurityValidator.validate_json_depth(v)
        return v

class SecurePromptArgs(BaseModel):
    """Validation for prompt arguments in POST /prompts/{name}"""
    model_config = ConfigDict(str_strip_whitespace=True)
    
    args: Dict[str, str] = {}
    
    @field_validator('args')
    @classmethod
    def validate_args(cls, v: Dict[str, str]) -> Dict[str, str]:
        """Validate prompt arguments"""
        for key, value in v.items():
            # Validate keys
            if not re.match(r'^[a-zA-Z0-9_]+$', key):
                raise ValueError(f"Invalid argument key: {key}")
            # Sanitize values that might be displayed
            v[key] = SecurityValidator.sanitize_display_text(value, f"Argument {key}")
        return v

C. Update Route Handlers:

# app/main.py - Apply secure models

# Server routes
@server_router.post("", response_model=ServerRead)
async def create_server(
    server: SecureServerCreate,  # Changed from ServerCreate
    db: Session = Depends(get_db),
    user: str = Depends(require_auth),
) -> ServerRead:
    # Input is now validated
    pass

# Tool routes
@tool_router.post("", response_model=ToolRead)
async def create_tool(
    tool: SecureToolCreate,  # Changed from ToolCreate
    db: Session = Depends(get_db),
    user: str = Depends(require_auth)
) -> ToolRead:
    # Input is now validated
    pass

# Similar changes for all other endpoints...

D. Special Handling for Dynamic Routes:

# Validate path parameters
@resource_router.get("/{uri:path}")
async def read_resource(
    uri: str,
    db: Session = Depends(get_db),
    user: str = Depends(require_auth)
) -> ResourceContent:
    # Validate URI parameter
    try:
        validated_uri = SecurityValidator.validate_identifier(uri, "Resource URI")
    except ValueError as e:
        raise HTTPException(status_code=400, detail=str(e))
    
    # Continue with validated URI
    pass

# RPC endpoint with dynamic method validation
@utility_router.post("/rpc")
async def handle_rpc(
    request: Request,
    db: Session = Depends(get_db),
    user: str = Depends(require_auth)
):
    body = await request.json()
    
    # Validate as SecureRPCRequest first
    try:
        rpc_request = SecureRPCRequest(**body)
    except ValidationError as e:
        raise HTTPException(status_code=422, detail=e.errors())
    
    # Method-specific validation
    if rpc_request.method == "prompts/get":
        # Additional validation for prompt-specific params
        pass

Testing Requirements:

  • Test each endpoint with the same payloads as admin endpoints
  • Test protocol-specific endpoints with malformed requests
  • Test RPC endpoint with various method calls
  • Test WebSocket endpoints with invalid JSON
  • Verify path parameter validation
  • Test with MCP protocol compliance

Acceptance Criteria:

  • All non-admin endpoints validate input using the same framework as admin endpoints
  • Reuse validation models from Issue 1 where applicable
  • Path parameters and query parameters are validated
  • Protocol compliance is maintained (MCP spec)
  • WebSocket and SSE endpoints handle invalid input gracefully
  • Error messages are consistent with admin endpoints
  • Performance impact remains < 10ms per request

Note: This implementation leverages the validation framework established in Issue 1 (#339) to ensure consistent security practices across all API endpoints. The same SecurityValidator class and configuration settings are reused to maintain uniformity in validation rules and limits.

Metadata

Metadata

Assignees

Labels

testingTesting (unit, e2e, manual, automated, etc)triageIssues / Features awaiting triage

Type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions