Skip to content

[CHORE][TESTING]: Implement fuzz testing automation #256

@crivetimihai

Description

@crivetimihai

🧭 Chore Summary

Implement comprehensive fuzz testing automation across the entire MCP Gateway codebase: establish robust property-based testing with make fuzz-all, make fuzz-hypothesis, make fuzz-api, and make fuzz-security targets, achieving deep coverage of JSON-RPC validation, JSONPath processing, API endpoints, and stateful business logic to uncover edge cases, security vulnerabilities, and crashes before they reach production.


🧱 Areas Affected

  • Test infrastructure / Fuzz testing framework setup
  • Build system / Make targets (make fuzz-all, make fuzz-hypothesis, make fuzz-api, make fuzz-security)
  • GitHub Actions / CI pipeline (nightly and PR-based fuzzing)
  • Core validation logic (JSON-RPC, JSONPath, schema validation)
  • API endpoints coverage (OpenAPI-driven fuzzing)
  • Stateful API workflows (authentication, CRUD sequences)
  • Security testing (input sanitization, injection attacks)
  • Native code integration (C extensions, memory safety)

⚙️ Context / Rationale

Fuzz testing ensures that every code path handles unexpected inputs gracefully without crashes, security vulnerabilities, or undefined behavior. By combining property-based testing, coverage-guided fuzzing, schema-driven API testing, and stateful security testing, we create a comprehensive safety net that discovers edge cases no human would think to test. This is critical for a gateway that processes arbitrary JSON-RPC requests, JSONPath expressions, and user-generated content.

What is Fuzz Testing?
Fuzz testing automatically generates thousands of random, malformed, or edge-case inputs to find bugs, crashes, security vulnerabilities, and unexpected behaviors. Different fuzzing approaches target different layers of the application stack.

MCP Gateway Fuzz Testing Architecture:

graph TD
    A[Fuzz Testing Suite] --> B[Property-Based Testing]
    A --> C[Coverage-Guided Fuzzing]
    A --> D[API Schema Fuzzing]
    A --> E[Stateful Security Fuzzing]
    
    B --> B1[Hypothesis - Pure Python Logic]
    B --> B2[JSON-RPC Validation]
    B --> B3[JSONPath Processing]
    B --> B4[Schema Validators]
    
    C --> C1[Atheris - Native Code]
    C --> C2[Memory Safety]
    C --> C3[C Extension Fuzzing]
    C --> C4[Deep Logic Paths]
    
    D --> D1[Schemathesis - OpenAPI]
    D --> D2[Endpoint Fuzzing]
    D --> D3[Contract Validation]
    D --> D4[Response Verification]
    
    E --> E1[RESTler - Stateful]
    E --> E2[Auth Sequences]
    E --> E3[CRUD Workflows]
    E --> E4[Security Injections]
Loading

Hypothesis Property-Based Testing Example:

# tests/fuzz/test_jsonrpc_fuzz.py
from hypothesis import given, strategies as st, settings, example
import pytest
import json
from mcpgateway.validation.jsonrpc import validate_request, JSONRPCError

class TestJSONRPCFuzzing:
    @given(st.binary())
    @example(b"")  # Empty binary
    @example(b"\x00\x01\x02")  # Non-UTF8 bytes
    def test_validate_request_handles_binary_input(self, raw_bytes):
        """Test that binary input never crashes the validator."""
        try:
            # This should either parse successfully or raise JSONRPCError
            validate_request(raw_bytes)
        except (JSONRPCError, ValueError, TypeError, UnicodeDecodeError):
            # These are acceptable exceptions
            pass
        except Exception as e:
            pytest.fail(f"Unexpected exception: {type(e).__name__}: {e}")

    @given(st.text())
    @example("")  # Empty string
    @example("null")  # Valid JSON but invalid request
    @example('{"incomplete":')  # Malformed JSON
    def test_validate_request_handles_text_input(self, text_input):
        """Test that text input never crashes the validator."""
        try:
            validate_request(text_input)
        except (JSONRPCError, ValueError, TypeError, json.JSONDecodeError):
            pass
        except Exception as e:
            pytest.fail(f"Unexpected exception: {type(e).__name__}: {e}")

    @given(st.dictionaries(
        keys=st.text(min_size=1, max_size=50),
        values=st.recursive(
            st.one_of(st.none(), st.booleans(), st.integers(), st.floats(), st.text()),
            lambda children: st.lists(children) | st.dictionaries(st.text(), children),
            max_leaves=20
        ),
        max_size=20
    ))
    def test_validate_request_handles_arbitrary_dicts(self, data):
        """Test arbitrary dictionary structures."""
        try:
            validate_request(data)
        except (JSONRPCError, ValueError, TypeError):
            pass
        except Exception as e:
            pytest.fail(f"Unexpected exception: {type(e).__name__}: {e}")

JSONPath Fuzzing Example:

# tests/fuzz/test_jsonpath_fuzz.py
from hypothesis import given, strategies as st, assume
import pytest
from mcpgateway.config import jsonpath_modifier

class TestJSONPathFuzzing:
    @given(st.text(min_size=1, max_size=200))
    def test_jsonpath_modifier_never_crashes(self, path_expression):
        """Test that arbitrary JSONPath expressions never crash."""
        try:
            result = jsonpath_modifier(path_expression)
            # If it succeeds, result should be valid
            assert result is not None
        except (ValueError, TypeError, AttributeError, KeyError, IndexError):
            # These are acceptable exceptions for invalid paths
            pass
        except Exception as e:
            pytest.fail(f"Unexpected exception: {type(e).__name__}: {e}")

    @given(st.text().filter(lambda x: '$' in x))
    def test_jsonpath_with_dollar_expressions(self, expression):
        """Test JSONPath expressions containing $ operators."""
        try:
            jsonpath_modifier(expression)
        except (ValueError, TypeError, AttributeError, KeyError, IndexError):
            pass
        except Exception as e:
            pytest.fail(f"Unexpected exception: {type(e).__name__}: {e}")

Atheris Coverage-Guided Fuzzing Example:

# fuzzers/fuzz_jsonpath.py
#!/usr/bin/env python3
"""Coverage-guided fuzzing for JSONPath processing."""
import atheris
import sys
import json
from mcpgateway.config import jsonpath_modifier

def TestOneInput(data):
    """Fuzz target for JSONPath processing."""
    fdp = atheris.FuzzedDataProvider(data)
    
    try:
        # Generate various JSONPath expressions
        if fdp.remaining_bytes() < 1:
            return
            
        choice = fdp.ConsumeIntInRange(0, 3)
        
        if choice == 0:
            # Simple path expression
            path = "$." + fdp.ConsumeUnicodeNoSurrogates(50)
        elif choice == 1:
            # Array access
            path = "$[" + str(fdp.ConsumeIntInRange(0, 1000)) + "]"
        elif choice == 2:
            # Complex expression
            path = fdp.ConsumeUnicodeNoSurrogates(100)
        else:
            # Raw input
            path = fdp.ConsumeString(200)
            
        # Test JSONPath modifier (should never crash)
        jsonpath_modifier(path)
        
    except (ValueError, TypeError, AttributeError, KeyError, IndexError):
        # Expected exceptions for invalid input
        pass
    except Exception:
        # Unexpected exceptions should be caught by Atheris
        raise

def main():
    atheris.instrument_all()
    atheris.Setup(sys.argv, TestOneInput)
    atheris.Fuzz()

if __name__ == "__main__":
    main()

Schemathesis API Fuzzing Integration:

# tests/fuzz/test_api_schema_fuzz.py
import schemathesis
import pytest
from fastapi.testclient import TestClient
from mcpgateway.main import app

# Create schema from FastAPI app
schema = schemathesis.from_asgi("/openapi.json", app)

@schema.parametrize()
@pytest.mark.fuzz
def test_api_endpoints_never_crash(case):
    """Test that all API endpoints handle arbitrary valid inputs."""
    # Add authentication headers
    case.headers = case.headers or {}
    case.headers.update({
        "Authorization": "Basic YWRtaW46Y2hhbmdlbWU="  # admin:changeme
    })
    
    # Execute the test case
    response = case.call_asgi(app)
    
    # Verify response is well-formed
    case.validate_response(response)
    
    # Additional checks
    assert response.status_code < 500, f"Server error: {response.status_code}"
    
    if response.headers.get("content-type", "").startswith("application/json"):
        try:
            response.json()  # Should parse as valid JSON
        except ValueError:
            pytest.fail("Response claimed to be JSON but was not parseable")

class TestAPIFuzzingCustom:
    def test_jsonrpc_endpoint_fuzzing(self):
        """Custom fuzzing for JSON-RPC endpoints."""
        client = TestClient(app)
        
        test_cases = [
            {"jsonrpc": "2.0", "method": "ping", "id": 1},
            {"jsonrpc": "2.0", "method": "initialize", "params": {}, "id": 2},
            {"jsonrpc": "2.0", "method": "tools/list", "id": 3},
            # Malformed cases
            {"jsonrpc": "1.0", "method": "ping", "id": 1},  # Wrong version
            {"method": "ping", "id": 1},  # Missing jsonrpc
            {"jsonrpc": "2.0", "id": 1},  # Missing method
        ]
        
        for case in test_cases:
            response = client.post(
                "/rpc",
                json=case,
                headers={"Authorization": "Basic YWRtaW46Y2hhbmdlbWU="}
            )
            # Should not crash (may return error responses)
            assert response.status_code in [200, 400, 401, 422]

RESTler Stateful Security Fuzzing Configuration:

# fuzzers/restler_config.py
"""Configuration for RESTler stateful fuzzing."""

RESTLER_CONFIG = {
    "RestlerRootDirPath": "restler_results",
    "SwaggerSpecFilePath": "openapi.json",
    "GrammarOutputDirectoryPath": "restler_grammar",
    "ReplayLogFilePath": "restler_replay.json",
    "Settings": {
        "max_combinations": 10000,
        "max_request_execution_time": 30,
        "target_ip": "127.0.0.1",
        "target_port": 4444,
        "authentication": {
            "token": {
                "type": "basic",
                "username": "admin", 
                "password": "changeme"
            }
        },
        "checkers": {
            "enable_all": True,
            "enable_body_utf8_checking": True,
            "enable_response_time_checking": True,
            "enable_memory_leak_checking": True
        }
    }
}

📦 Related Make Targets

Target Purpose
make fuzz-all Run complete fuzzing suite (hypothesis + atheris + api + security)
make fuzz-hypothesis Property-based testing with Hypothesis
make fuzz-atheris Coverage-guided fuzzing with Atheris
make fuzz-api Schema-driven API fuzzing with Schemathesis
make fuzz-security Stateful security fuzzing with RESTler
make fuzz-install Install all fuzzing dependencies
make fuzz-corpus Generate and manage fuzz testing corpora
make fuzz-report Generate comprehensive fuzz testing report
make fuzz-clean Clean fuzzing artifacts and results
make fuzz-regression Run regression tests for known issues
make fuzz-quick Fast fuzzing for CI/PR validation
make fuzz-extended Extended fuzzing for nightly runs

Bold targets are mandatory; CI must fail if fuzzing discovers crashes, security issues, or unexpected exceptions.


📋 Acceptance Criteria

  • make fuzz-all completes successfully with 0 crashes and 0 security issues.
  • Hypothesis tests cover all pure Python validation logic with property-based testing.
  • Atheris fuzzing targets JSONPath, JSON-RPC, and other native code paths.
  • Schemathesis validates all OpenAPI endpoints with comprehensive input generation.
  • RESTler performs stateful security testing of authentication and CRUD workflows.
  • Fuzzing corpus is maintained with interesting test cases for regression testing.
  • CI integration runs quick fuzzing on PRs and extended fuzzing nightly.
  • All fuzzing tools generate structured reports with reproducible test cases.
  • Security checkers enabled for injection attacks, memory leaks, and timing issues.
  • Performance benchmarks ensure fuzzing doesn't introduce significant overhead.
  • Documentation provides clear guidance on interpreting and acting on fuzz findings.
  • Regression tests prevent reintroduction of previously discovered issues.

🛠️ Task List (suggested flow)

  1. Install fuzzing dependencies and setup

    # Install core fuzzing tools
    pip install hypothesis pytest-hypothesis
    pip install atheris
    pip install schemathesis
    
    # Install RESTler (requires .NET)
    git clone https://github.com/microsoft/restler-fuzzer.git
    cd restler-fuzzer && ./build.sh

    Create requirements-fuzz.txt:

    hypothesis>=6.90.0
    pytest-hypothesis>=0.19.0
    atheris>=2.3.0
    schemathesis>=3.19.0
    pytest-xdist>=3.0.0
    coverage>=7.0.0
  2. Makefile integration

    .PHONY: fuzz-all fuzz-hypothesis fuzz-atheris fuzz-api fuzz-security \
            fuzz-install fuzz-corpus fuzz-report fuzz-clean fuzz-quick
    
    # Install all fuzzing dependencies
    fuzz-install:
    	@echo "🔧  Installing fuzzing dependencies..."
    	pip install -r requirements-fuzz.txt
    	@echo "✅  Fuzzing tools installed"
    
    # Run complete fuzzing suite
    fuzz-all: fuzz-hypothesis fuzz-atheris fuzz-api fuzz-security
    	@echo "🎯  Complete fuzzing suite finished"
    	make fuzz-report
    
    # Property-based testing with Hypothesis
    fuzz-hypothesis:
    	@echo "🧪  Running Hypothesis property-based tests..."
    	python -m pytest tests/fuzz/ -v \
    		--hypothesis-show-statistics \
    		--hypothesis-profile=dev \
    		-x
    
    # Coverage-guided fuzzing with Atheris  
    fuzz-atheris:
    	@echo "🎭  Running Atheris coverage-guided fuzzing..."
    	@mkdir -p corpus fuzzers/results
    	python fuzzers/fuzz_jsonpath.py -runs=10000 -max_total_time=300 \
    		-artifact_prefix=fuzzers/results/ -print_final_stats=1
    	python fuzzers/fuzz_jsonrpc.py -runs=10000 -max_total_time=300 \
    		-artifact_prefix=fuzzers/results/ -print_final_stats=1
    
    # Schema-driven API fuzzing
    fuzz-api:
    	@echo "🌐  Running Schemathesis API fuzzing..."
    	@echo "Starting test server..."
    	make run-dev &
    	sleep 10
    	schemathesis run http://localhost:4444/openapi.json \
    		--checks all \
    		--hypothesis-max-examples=1000 \
    		--auth admin:changeme \
    		--workers 4 \
    		--report=reports/schemathesis-report.json
    	pkill -f "make run-dev" || true
    
    # Stateful security fuzzing with RESTler
    fuzz-security:
    	@echo "🔐  Running RESTler stateful security fuzzing..."
    	@mkdir -p restler_results
    	make run-dev &
    	sleep 10
    	curl -o openapi.json http://localhost:4444/openapi.json
    	restler-fuzzer/restler/Restler compile --api_spec openapi.json
    	restler-fuzzer/restler/Restler fuzz \
    		--grammar_file Compile/grammar.py \
    		--dictionary_file Compile/dict.json \
    		--settings Compile/engine_settings.json \
    		--time_budget 0:30:00 \
    		--target_ip 127.0.0.1 \
    		--target_port 4444
    	pkill -f "make run-dev" || true
    
    # Quick fuzzing for CI
    fuzz-quick:
    	@echo "⚡  Running quick fuzzing for CI..."
    	python -m pytest tests/fuzz/ -v \
    		--hypothesis-max-examples=100 \
    		-x
    	python fuzzers/fuzz_jsonpath.py -runs=1000 -max_total_time=60
    
    # Generate fuzzing report
    fuzz-report:
    	@echo "📊  Generating fuzzing report..."
    	python scripts/generate_fuzz_report.py
    
    # Clean fuzzing artifacts
    fuzz-clean:
    	@echo "🧹  Cleaning fuzzing artifacts..."
    	rm -rf corpus/ fuzzers/results/ restler_results/ 
    	rm -f openapi.json reports/schemathesis-report.json
  3. Directory structure setup

    tests/fuzz/
    ├── __init__.py
    ├── conftest.py                     # Fuzzing test configuration
    ├── test_jsonrpc_fuzz.py           # JSON-RPC validation fuzzing
    ├── test_jsonpath_fuzz.py          # JSONPath processing fuzzing
    ├── test_schema_validation_fuzz.py # Pydantic schema fuzzing
    ├── test_api_schema_fuzz.py        # Schemathesis integration
    └── test_security_fuzz.py          # Security-focused fuzzing
    
    fuzzers/
    ├── fuzz_jsonpath.py               # Atheris JSONPath fuzzer
    ├── fuzz_jsonrpc.py               # Atheris JSON-RPC fuzzer
    ├── fuzz_config_parser.py         # Atheris config parsing fuzzer
    ├── restler_config.py             # RESTler configuration
    └── results/                       # Fuzzing artifacts
    
    corpus/
    ├── jsonpath/                      # JSONPath test cases
    ├── jsonrpc/                      # JSON-RPC test cases
    └── api/                          # API request test cases
    
    scripts/
    └── generate_fuzz_report.py       # Report generation
    
  4. Core fuzzing test implementation

    # tests/fuzz/conftest.py
    import pytest
    from hypothesis import settings, Verbosity
    
    # Configure Hypothesis for fuzzing
    settings.register_profile("dev", max_examples=100, verbosity=Verbosity.normal)
    settings.register_profile("ci", max_examples=50, verbosity=Verbosity.quiet)
    settings.register_profile("thorough", max_examples=1000, verbosity=Verbosity.verbose)
    
    @pytest.fixture(scope="session")
    def fuzz_settings():
        """Configure fuzzing settings based on environment."""
        import os
        profile = os.getenv("HYPOTHESIS_PROFILE", "dev")
        settings.load_profile(profile)
    # tests/fuzz/test_schema_validation_fuzz.py
    from hypothesis import given, strategies as st
    import pytest
    from mcpgateway.schemas import ToolCreate, ResourceCreate, PromptCreate
    
    class TestSchemaValidationFuzzing:
        @given(st.dictionaries(
            keys=st.text(min_size=1, max_size=50),
            values=st.one_of(
                st.none(), st.booleans(), st.integers(),
                st.floats(), st.text(), st.lists(st.text())
            )
        ))
        def test_tool_create_schema_robust(self, data):
            """Test ToolCreate schema with arbitrary data."""
            try:
                tool = ToolCreate(**data)
                # If validation succeeds, object should be valid
                assert tool.name is not None
            except (ValueError, TypeError):
                # Expected for invalid data
                pass
    
        @given(st.text(min_size=0, max_size=10000))
        def test_tool_create_name_field(self, name):
            """Test tool name field with various string inputs."""
            try:
                tool = ToolCreate(name=name, url="http://example.com")
                assert len(tool.name) >= 1  # Name should not be empty after validation
            except ValueError:
                # Expected for invalid names
                pass
  5. Atheris native code fuzzing

    # fuzzers/fuzz_jsonrpc.py
    #!/usr/bin/env python3
    """Coverage-guided fuzzing for JSON-RPC validation."""
    import atheris
    import sys
    import json
    from mcpgateway.validation.jsonrpc import validate_request, JSONRPCError
    
    def TestOneInput(data):
        """Fuzz target for JSON-RPC validation."""
        fdp = atheris.FuzzedDataProvider(data)
        
        try:
            if fdp.remaining_bytes() < 1:
                return
                
            choice = fdp.ConsumeIntInRange(0, 4)
            
            if choice == 0:
                # Valid-ish JSON-RPC structure
                request = {
                    "jsonrpc": fdp.ConsumeUnicodeNoSurrogates(10),
                    "method": fdp.ConsumeUnicodeNoSurrogates(50),
                    "id": fdp.ConsumeIntInRange(0, 1000000)
                }
                if fdp.ConsumeBool():
                    request["params"] = {"test": fdp.ConsumeUnicodeNoSurrogates(100)}
            elif choice == 1:
                # Malformed JSON structure
                request = fdp.ConsumeUnicodeNoSurrogates(200)
            elif choice == 2:
                # Binary data
                request = fdp.ConsumeBytes(100)
            elif choice == 3:
                # Random dictionary
                request = {}
                for _ in range(fdp.ConsumeIntInRange(0, 10)):
                    key = fdp.ConsumeUnicodeNoSurrogates(20)
                    value = fdp.ConsumeUnicodeNoSurrogates(50)
                    request[key] = value
            else:
                # Edge cases
                request = None if fdp.ConsumeBool() else []
                
            # Test validation (should never crash)
            validate_request(request)
            
        except (JSONRPCError, ValueError, TypeError, json.JSONDecodeError, KeyError):
            # Expected exceptions
            pass
        except Exception:
            # Unexpected - let Atheris catch it
            raise
    
    def main():
        atheris.instrument_all()
        atheris.Setup(sys.argv, TestOneInput)
        atheris.Fuzz()
    
    if __name__ == "__main__":
        main()
  6. Schemathesis API testing integration

    # tests/fuzz/test_api_comprehensive_fuzz.py
    import schemathesis
    from fastapi.testclient import TestClient
    from mcpgateway.main import app
    import pytest
    
    # Load schema from the FastAPI app
    schema = schemathesis.from_asgi("/openapi.json", app)
    
    class TestAPIFuzzingComprehensive:
        @schema.parametrize(endpoint="/admin/tools")
        def test_tools_endpoint_fuzzing(self, case):
            """Fuzz the tools management endpoints."""
            case.headers = case.headers or {}
            case.headers["Authorization"] = "Basic YWRtaW46Y2hhbmdlbWU="
            
            response = case.call_asgi(app)
            case.validate_response(response)
            
            # Tools endpoints should never return 500
            assert response.status_code < 500
    
        @schema.parametrize(endpoint="/rpc")
        def test_jsonrpc_endpoint_fuzzing(self, case):
            """Fuzz the JSON-RPC endpoint."""
            case.headers = case.headers or {}
            case.headers["Authorization"] = "Basic YWRtaW46Y2hhbmdlbWU="
            case.headers["Content-Type"] = "application/json"
            
            response = case.call_asgi(app)
            
            # JSON-RPC should handle malformed requests gracefully
            assert response.status_code in [200, 400, 401, 422]
    
        def test_authentication_fuzzing(self):
            """Test authentication with various malformed credentials."""
            client = TestClient(app)
            
            auth_variants = [
                "Basic invalid",
                "Bearer token123",
                "Basic " + "x" * 1000,  # Very long auth
                "Digest username=test",
                "",
                None
            ]
            
            for auth in auth_variants:
                headers = {"Authorization": auth} if auth else {}
                response = client.get("/admin/tools", headers=headers)
                # Should return 401 or handle gracefully
                assert response.status_code in [401, 400, 422]
  7. Security-focused fuzzing

    # tests/fuzz/test_security_fuzz.py
    from hypothesis import given, strategies as st
    import pytest
    from fastapi.testclient import TestClient
    from mcpgateway.main import app
    
    class TestSecurityFuzzing:
        @given(st.text(min_size=1, max_size=1000))
        def test_sql_injection_resistance(self, malicious_input):
            """Test resistance to SQL injection in various fields."""
            client = TestClient(app)
            
            # Test in tool name field
            payload = {
                "name": malicious_input,
                "url": "http://example.com",
                "description": "test"
            }
            
            response = client.post(
                "/admin/tools",
                json=payload,
                headers={"Authorization": "Basic YWRtaW46Y2hhbmdlbWU="}
            )
            
            # Should not crash, may reject invalid input
            assert response.status_code in [200, 201, 400, 422]
    
        @given(st.text().filter(lambda x: any(char in x for char in '<>"\'&')))
        def test_xss_prevention(self, potentially_malicious):
            """Test XSS prevention in user inputs."""
            client = TestClient(app)
            
            # Test in description field that might be rendered
            payload = {
                "name": "test-tool",
                "url": "http://example.com", 
                "description": potentially_malicious
            }
            
            response = client.post(
                "/admin/tools",
                json=payload,
                headers={"Authorization": "Basic YWRtaW46Y2hhbmdlbWU="}
            )
            
            # Should handle potentially malicious content safely
            assert response.status_code in [200, 201, 400, 422]
            
            if response.status_code in [200, 201]:
                # If accepted, should be properly escaped in responses
                tool_response = client.get(
                    "/admin/tools",
                    headers={"Authorization": "Basic YWRtaW46Y2hhbmdlbWU="}
                )
                # Raw HTML tags should not appear in JSON responses
                assert "<script>" not in tool_response.text.lower()
    
        @given(st.integers(min_value=-2**31, max_value=2**31))
        def test_integer_overflow_handling(self, large_int):
            """Test handling of integer overflow in numeric fields."""
            client = TestClient(app)
            
            # Test in ID fields and numeric parameters
            response = client.get(
                f"/admin/tools/{large_int}",
                headers={"Authorization": "Basic YWRtaW46Y2hhbmdlbWU="}
            )
            
            # Should handle large integers gracefully
            assert response.status_code in [200, 400, 404, 422]
  8. Corpus management and reporting

    # scripts/generate_fuzz_report.py
    #!/usr/bin/env python3
    """Generate comprehensive fuzzing report."""
    import json
    import os
    from pathlib import Path
    from datetime import datetime
    
    def generate_report():
        """Generate comprehensive fuzzing report."""
        report = {
            "timestamp": datetime.now().isoformat(),
            "summary": {},
            "tools": {},
            "findings": [],
            "corpus_stats": {}
        }
        
        # Hypothesis statistics
        if Path("reports/hypothesis-stats.json").exists():
            with open("reports/hypothesis-stats.json") as f:
                report["tools"]["hypothesis"] = json.load(f)
        
        # Atheris results
        atheris_results = Path("fuzzers/results")
        if atheris_results.exists():
            report["tools"]["atheris"] = {
                "artifacts": len(list(atheris_results.glob("*"))),
                "results_dir": str(atheris_results)
            }
        
        # Schemathesis results
        if Path("reports/schemathesis-report.json").exists():
            with open("reports/schemathesis-report.json") as f:
                report["tools"]["schemathesis"] = json.load(f)
        
        # RESTler results
        restler_results = Path("restler_results")
        if restler_results.exists():
            report["tools"]["restler"] = {
                "findings": len(list(restler_results.glob("**/bug_buckets/*"))),
                "results_dir": str(restler_results)
            }
        
        # Corpus statistics
        corpus_dir = Path("corpus")
        if corpus_dir.exists():
            for subdir in corpus_dir.iterdir():
                if subdir.is_dir():
                    report["corpus_stats"][subdir.name] = len(list(subdir.glob("*")))
        
        # Write report
        report_file = Path("reports/fuzz-report.json")
        report_file.parent.mkdir(exist_ok=True)
        with open(report_file, "w") as f:
            json.dump(report, f, indent=2)
        
        print(f"📊 Fuzzing report generated: {report_file}")
        
        # Print summary
        print("\n🎯 Fuzzing Summary:")
        for tool, results in report["tools"].items():
            print(f"  {tool}: {len(results) if isinstance(results, list) else 'completed'}")
    
    if __name__ == "__main__":
        generate_report()
  9. CI/CD integration

    # .github/workflows/fuzz-testing.yml
    name: Fuzz Testing
    
    on:
      schedule:
        - cron: '0 2 * * *'  # Nightly at 2 AM
      push:
        branches: [main]
      pull_request:
        branches: [main]
    
    jobs:
      fuzz-quick:
        name: Quick Fuzzing (PR)
        runs-on: ubuntu-latest
        if: github.event_name == 'pull_request'
        
        steps:
          - name: ⬇️ Checkout source
            uses: actions/checkout@v4
          
          - name: 🐍 Set up Python
            uses: actions/setup-python@v5
            with:
              python-version: "3.12"
              cache: pip
          
          - name: 📦 Install dependencies
            run: |
              python -m pip install --upgrade pip
              pip install -e .[dev]
              make fuzz-install
          
          - name: ⚡ Run quick fuzzing
            run: make fuzz-quick
            timeout-minutes: 10
          
          - name: 📊 Upload results
            uses: actions/upload-artifact@v4
            if: always()
            with:
              name: quick-fuzz-results
              path: reports/
    
      fuzz-comprehensive:
        name: Comprehensive Fuzzing (Nightly)
        runs-on: ubuntu-latest
        if: github.event_name == 'schedule' || github.event_name == 'push'
        
        strategy:
          matrix:
            tool: [hypothesis, atheris, api, security]
        
        steps:
          - name: ⬇️ Checkout source
            uses: actions/checkout@v4
          
          - name: 🐍 Set up Python
            uses: actions/setup-python@v5
            with:
              python-version: "3.12"
              cache: pip
          
          - name: 📦 Install dependencies
            run: |
              python -m pip install --upgrade pip
              pip install -e .[dev]
              make fuzz-install
          
          - name: 🧪 Run fuzzing - ${{ matrix.tool }}
            run: make fuzz-${{ matrix.tool }}
            timeout-minutes: 60
          
          - name: 📊 Upload results
            uses: actions/upload-artifact@v4
            if: always()
            with:
              name: fuzz-results-${{ matrix.tool }}
              path: |
                reports/
                fuzzers/results/
                restler_results/
                corpus/
    
      fuzz-report:
        name: Generate Fuzz Report
        runs-on: ubuntu-latest
        needs: [fuzz-comprehensive]
        if: github.event_name == 'schedule' || github.event_name == 'push'
        
        steps:
          - name: ⬇️ Checkout source
            uses: actions/checkout@v4
          
          - name: 📥 Download all artifacts
            uses: actions/download-artifact@v4
            with:
              path: artifacts/
          
          - name: 📊 Generate comprehensive report
            run: |
              python scripts/generate_fuzz_report.py
              cat reports/fuzz-report.json
          
          - name: 🚨 Check for critical findings
            run: |
              python -c "
              import json
              with open('reports/fuzz-report.json') as f:
                  report = json.load(f)
              
              critical_findings = [f for f in report.get('findings', []) 
                                  if f.get('severity') == 'critical']
              
              if critical_findings:
                  print(f'🚨 Found {len(critical_findings)} critical findings!')
                  exit(1)
              else:
                  print('✅ No critical findings detected')
              "
  10. Documentation and best practices

    # docs/fuzzing.md
    """
    # Fuzz Testing Guide
    
    ## Overview
    This document describes the comprehensive fuzz testing setup for MCP Gateway.
    
    ## Tools Used
    - **Hypothesis**: Property-based testing for pure Python logic
    - **Atheris**: Coverage-guided fuzzing for native code paths
    - **Schemathesis**: OpenAPI schema-driven API fuzzing
    - **RESTler**: Stateful security-focused API fuzzing
    
    ## Running Fuzzing Tests
    
    ### Quick Fuzzing (CI/PR)
    ```bash
    make fuzz-quick

    Full Fuzzing Suite

    make fuzz-all

    Individual Tools

    make fuzz-hypothesis    # Property-based testing
    make fuzz-atheris      # Coverage-guided fuzzing
    make fuzz-api          # API schema fuzzing
    make fuzz-security     # Security fuzzing

    Interpreting Results

    Hypothesis Failures

    • Review minimal failing examples
    • Add them as regression tests
    • Fix underlying logic issues

    Atheris Crashes

    • Examine crash artifacts in fuzzers/results/
    • Use addr2line for C stack traces
    • Report upstream if in dependencies

    Schemathesis Failures

    • Check for 5xx responses in unexpected cases
    • Verify OpenAPI schema accuracy
    • Improve input validation

    RESTler Security Issues

    • Review bug buckets in restler_results/
    • Prioritize authentication bypasses
    • Fix injection vulnerabilities
      """
    
    

📖 References


🧩 Additional Notes

  • Start incrementally: Begin with Hypothesis tests for core validation logic, then expand to native code and API fuzzing.
  • Corpus management: Save interesting test cases that uncover edge cases for regression testing and faster fuzzing.
  • Security focus: Prioritize fuzzing authentication, input validation, and data processing paths for security vulnerabilities.
  • Performance awareness: Monitor fuzzing overhead and use timeouts to prevent resource exhaustion.
  • CI integration: Run quick fuzzing on PRs and comprehensive fuzzing nightly to balance speed and coverage.
  • Artifact preservation: Save crashes, timeouts, and security findings for detailed analysis and bug reporting.
  • Regression prevention: Convert fuzzing discoveries into permanent test cases to prevent reintroduction.
  • Documentation updates: Keep fuzzing documentation current with tool configurations and interpretation guides.

Fuzz Testing Best Practices for MCP Gateway:

  • Focus on JSON-RPC validation, JSONPath processing, and schema validation as high-value targets
  • Use property-based testing to validate invariants in core business logic
  • Combine multiple fuzzing approaches for comprehensive coverage
  • Maintain seed corpora for consistent and fast fuzzing runs
  • Integrate security-focused fuzzing to catch injection and authentication bypasses
  • Monitor fuzzing metrics and adjust timeouts and coverage targets based on findings
  • Document all critical findings and ensure they become regression tests

Metadata

Metadata

Assignees

Labels

choreLinting, formatting, dependency hygiene, or project maintenance chorescicdIssue with CI/CD process (GitHub Actions, scaffolding)devopsDevOps activities (containers, automation, deployment, makefiles, etc)help wantedExtra attention is neededtestingTesting (unit, e2e, manual, automated, etc)

Type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions