mcpsec 2.7.2__tar.gz → 2.7.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {mcpsec-2.7.2 → mcpsec-2.7.3}/CHANGELOG.md +17 -11
- {mcpsec-2.7.2 → mcpsec-2.7.3}/PKG-INFO +1 -1
- mcpsec-2.7.3/mcpsec/__init__.py +3 -0
- mcpsec-2.7.3/mcpsec/__main__.py +4 -0
- {mcpsec-2.7.2 → mcpsec-2.7.3}/mcpsec/client/mcp_client.py +37 -9
- {mcpsec-2.7.2 → mcpsec-2.7.3}/mcpsec/exploit/evidence.py +15 -8
- {mcpsec-2.7.2 → mcpsec-2.7.3}/mcpsec/fuzzer/chain/chain_engine.py +81 -32
- {mcpsec-2.7.2 → mcpsec-2.7.3}/mcpsec/fuzzer/evolve/engine.py +21 -3
- {mcpsec-2.7.2 → mcpsec-2.7.3}/mcpsec/fuzzer/evolve/mcp_mutators.py +2 -1
- {mcpsec-2.7.2 → mcpsec-2.7.3}/mcpsec/fuzzer/evolve/mutators.py +2 -0
- {mcpsec-2.7.2 → mcpsec-2.7.3}/mcpsec/fuzzer/fuzz_engine.py +184 -79
- {mcpsec-2.7.2 → mcpsec-2.7.3}/mcpsec/fuzzer/generators/__init__.py +2 -1
- {mcpsec-2.7.2 → mcpsec-2.7.3}/mcpsec/fuzzer/generators/base.py +1 -0
- mcpsec-2.7.3/mcpsec/fuzzer/generators/header_routing.py +185 -0
- {mcpsec-2.7.2 → mcpsec-2.7.3}/mcpsec/fuzzer/generators/method_mutations.py +11 -0
- {mcpsec-2.7.2 → mcpsec-2.7.3}/mcpsec/fuzzer/generators/protocol_state.py +139 -2
- {mcpsec-2.7.2 → mcpsec-2.7.3}/mcpsec/fuzzer/generators/protocol_violation.py +52 -6
- mcpsec-2.7.3/mcpsec/fuzzer/generators/schema_fuzzer.py +549 -0
- {mcpsec-2.7.2 → mcpsec-2.7.3}/mcpsec/fuzzer/generators/session_attacks.py +67 -8
- {mcpsec-2.7.2 → mcpsec-2.7.3}/mcpsec/fuzzer/transport/http_fuzzer.py +29 -2
- {mcpsec-2.7.2 → mcpsec-2.7.3}/mcpsec/rogue/server.py +17 -0
- {mcpsec-2.7.2 → mcpsec-2.7.3}/pyproject.toml +1 -1
- {mcpsec-2.7.2 → mcpsec-2.7.3}/shell.nix +1 -1
- {mcpsec-2.7.2 → mcpsec-2.7.3}/tests/servers/blind_test.py +16 -26
- mcpsec-2.7.3/tests/servers/compat.py +72 -0
- mcpsec-2.7.3/tests/servers/dangerous_server.py +74 -0
- {mcpsec-2.7.2 → mcpsec-2.7.3}/tests/servers/stress_test_server.py +18 -27
- mcpsec-2.7.3/tests/servers/vulnerable_db_server.py +59 -0
- mcpsec-2.7.3/tests/test_schema_fuzzer.py +212 -0
- mcpsec-2.7.2/mcpsec/__init__.py +0 -11
- mcpsec-2.7.2/tests/servers/dangerous_server.py +0 -80
- mcpsec-2.7.2/tests/servers/vulnerable_db_server.py +0 -67
- {mcpsec-2.7.2 → mcpsec-2.7.3}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {mcpsec-2.7.2 → mcpsec-2.7.3}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {mcpsec-2.7.2 → mcpsec-2.7.3}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {mcpsec-2.7.2 → mcpsec-2.7.3}/.github/workflows/ci.yml +0 -0
- {mcpsec-2.7.2 → mcpsec-2.7.3}/.github/workflows/release.yml +0 -0
- {mcpsec-2.7.2 → mcpsec-2.7.3}/.gitignore +0 -0
- {mcpsec-2.7.2 → mcpsec-2.7.3}/CONTRIBUTING.md +0 -0
- {mcpsec-2.7.2 → mcpsec-2.7.3}/LICENSE +0 -0
- {mcpsec-2.7.2 → mcpsec-2.7.3}/README.md +0 -0
- {mcpsec-2.7.2 → mcpsec-2.7.3}/SECURITY.md +0 -0
- {mcpsec-2.7.2 → mcpsec-2.7.3}/assets/demo.gif +0 -0
- {mcpsec-2.7.2 → mcpsec-2.7.3}/mcpsec/ai/__init__.py +0 -0
- {mcpsec-2.7.2 → mcpsec-2.7.3}/mcpsec/ai/ai_payload_generator.py +0 -0
- {mcpsec-2.7.2 → mcpsec-2.7.3}/mcpsec/ai/ai_taint_analyzer.py +0 -0
- {mcpsec-2.7.2 → mcpsec-2.7.3}/mcpsec/ai/ai_validator.py +0 -0
- {mcpsec-2.7.2 → mcpsec-2.7.3}/mcpsec/ai/finding_classifier.py +0 -0
- {mcpsec-2.7.2 → mcpsec-2.7.3}/mcpsec/ai/llm_client.py +0 -0
- {mcpsec-2.7.2 → mcpsec-2.7.3}/mcpsec/cli.py +0 -0
- {mcpsec-2.7.2 → mcpsec-2.7.3}/mcpsec/client/__init__.py +0 -0
- {mcpsec-2.7.2 → mcpsec-2.7.3}/mcpsec/config.py +0 -0
- {mcpsec-2.7.2 → mcpsec-2.7.3}/mcpsec/discovery.py +0 -0
- {mcpsec-2.7.2 → mcpsec-2.7.3}/mcpsec/engine.py +0 -0
- {mcpsec-2.7.2 → mcpsec-2.7.3}/mcpsec/exploit/__init__.py +0 -0
- {mcpsec-2.7.2 → mcpsec-2.7.3}/mcpsec/exploit/connection.py +0 -0
- {mcpsec-2.7.2 → mcpsec-2.7.3}/mcpsec/exploit/formatters.py +0 -0
- {mcpsec-2.7.2 → mcpsec-2.7.3}/mcpsec/exploit/mcp_repl.py +0 -0
- {mcpsec-2.7.2 → mcpsec-2.7.3}/mcpsec/exploit/payload_engine.py +0 -0
- {mcpsec-2.7.2 → mcpsec-2.7.3}/mcpsec/exploit/playbooks/__init__.py +0 -0
- {mcpsec-2.7.2 → mcpsec-2.7.3}/mcpsec/exploit/playbooks/command_injection.py +0 -0
- {mcpsec-2.7.2 → mcpsec-2.7.3}/mcpsec/exploit/playbooks/generic.py +0 -0
- {mcpsec-2.7.2 → mcpsec-2.7.3}/mcpsec/exploit/playbooks/path_traversal.py +0 -0
- {mcpsec-2.7.2 → mcpsec-2.7.3}/mcpsec/exploit/playbooks/prompt_injection.py +0 -0
- {mcpsec-2.7.2 → mcpsec-2.7.3}/mcpsec/exploit/playbooks/sandbox_escape.py +0 -0
- {mcpsec-2.7.2 → mcpsec-2.7.3}/mcpsec/exploit/playbooks/sqli.py +0 -0
- {mcpsec-2.7.2 → mcpsec-2.7.3}/mcpsec/exploit/playbooks/ssrf.py +0 -0
- {mcpsec-2.7.2 → mcpsec-2.7.3}/mcpsec/exploit/session.py +0 -0
- {mcpsec-2.7.2 → mcpsec-2.7.3}/mcpsec/exploit/state_tracker.py +0 -0
- {mcpsec-2.7.2 → mcpsec-2.7.3}/mcpsec/fuzzer/chain/__init__.py +0 -0
- {mcpsec-2.7.2 → mcpsec-2.7.3}/mcpsec/fuzzer/chain/chain_analyzer.py +0 -0
- {mcpsec-2.7.2 → mcpsec-2.7.3}/mcpsec/fuzzer/chain/chain_builder.py +0 -0
- {mcpsec-2.7.2 → mcpsec-2.7.3}/mcpsec/fuzzer/chain/chain_reporter.py +0 -0
- {mcpsec-2.7.2 → mcpsec-2.7.3}/mcpsec/fuzzer/chain/injection_points.py +0 -0
- {mcpsec-2.7.2 → mcpsec-2.7.3}/mcpsec/fuzzer/chain/state_extractor.py +0 -0
- {mcpsec-2.7.2 → mcpsec-2.7.3}/mcpsec/fuzzer/chain/state_manager.py +0 -0
- {mcpsec-2.7.2 → mcpsec-2.7.3}/mcpsec/fuzzer/evolve/__init__.py +0 -0
- {mcpsec-2.7.2 → mcpsec-2.7.3}/mcpsec/fuzzer/evolve/corpus.py +0 -0
- {mcpsec-2.7.2 → mcpsec-2.7.3}/mcpsec/fuzzer/evolve/feedback.py +0 -0
- {mcpsec-2.7.2 → mcpsec-2.7.3}/mcpsec/fuzzer/evolve/scheduler.py +0 -0
- {mcpsec-2.7.2 → mcpsec-2.7.3}/mcpsec/fuzzer/generators/boundary_testing.py +0 -0
- {mcpsec-2.7.2 → mcpsec-2.7.3}/mcpsec/fuzzer/generators/concurrency_attacks.py +0 -0
- {mcpsec-2.7.2 → mcpsec-2.7.3}/mcpsec/fuzzer/generators/deserialization.py +0 -0
- {mcpsec-2.7.2 → mcpsec-2.7.3}/mcpsec/fuzzer/generators/encoding_attacks.py +0 -0
- {mcpsec-2.7.2 → mcpsec-2.7.3}/mcpsec/fuzzer/generators/header_mutations.py +0 -0
- {mcpsec-2.7.2 → mcpsec-2.7.3}/mcpsec/fuzzer/generators/id_confusion.py +0 -0
- {mcpsec-2.7.2 → mcpsec-2.7.3}/mcpsec/fuzzer/generators/injection_payloads.py +0 -0
- {mcpsec-2.7.2 → mcpsec-2.7.3}/mcpsec/fuzzer/generators/integer_boundaries.py +0 -0
- {mcpsec-2.7.2 → mcpsec-2.7.3}/mcpsec/fuzzer/generators/json_edge_cases.py +0 -0
- {mcpsec-2.7.2 → mcpsec-2.7.3}/mcpsec/fuzzer/generators/malformed_json.py +0 -0
- {mcpsec-2.7.2 → mcpsec-2.7.3}/mcpsec/fuzzer/generators/memory_exhaustion.py +0 -0
- {mcpsec-2.7.2 → mcpsec-2.7.3}/mcpsec/fuzzer/generators/param_mutations.py +0 -0
- {mcpsec-2.7.2 → mcpsec-2.7.3}/mcpsec/fuzzer/generators/regex_dos.py +0 -0
- {mcpsec-2.7.2 → mcpsec-2.7.3}/mcpsec/fuzzer/generators/resource_exhaustion.py +0 -0
- {mcpsec-2.7.2 → mcpsec-2.7.3}/mcpsec/fuzzer/generators/timing_attacks.py +0 -0
- {mcpsec-2.7.2 → mcpsec-2.7.3}/mcpsec/fuzzer/generators/type_confusion.py +0 -0
- {mcpsec-2.7.2 → mcpsec-2.7.3}/mcpsec/fuzzer/generators/unicode_attacks.py +0 -0
- {mcpsec-2.7.2 → mcpsec-2.7.3}/mcpsec/fuzzer/transport/stdio_fuzzer.py +0 -0
- {mcpsec-2.7.2 → mcpsec-2.7.3}/mcpsec/models.py +0 -0
- {mcpsec-2.7.2 → mcpsec-2.7.3}/mcpsec/reporters/__init__.py +0 -0
- {mcpsec-2.7.2 → mcpsec-2.7.3}/mcpsec/reporters/json_report.py +0 -0
- {mcpsec-2.7.2 → mcpsec-2.7.3}/mcpsec/reporters/sarif_report.py +0 -0
- {mcpsec-2.7.2 → mcpsec-2.7.3}/mcpsec/rogue/__init__.py +0 -0
- {mcpsec-2.7.2 → mcpsec-2.7.3}/mcpsec/rogue/payloads.py +0 -0
- {mcpsec-2.7.2 → mcpsec-2.7.3}/mcpsec/rules/exclusions.yml +0 -0
- {mcpsec-2.7.2 → mcpsec-2.7.3}/mcpsec/rules/mcp-ast-sandbox-escape.yml +0 -0
- {mcpsec-2.7.2 → mcpsec-2.7.3}/mcpsec/rules/mcp-broad-command-injection.yml +0 -0
- {mcpsec-2.7.2 → mcpsec-2.7.3}/mcpsec/rules/mcp-broad-path-traversal.yml +0 -0
- {mcpsec-2.7.2 → mcpsec-2.7.3}/mcpsec/rules/mcp-broad-sql-injection.yml +0 -0
- {mcpsec-2.7.2 → mcpsec-2.7.3}/mcpsec/rules/mcp-capability-issues.yml +0 -0
- {mcpsec-2.7.2 → mcpsec-2.7.3}/mcpsec/rules/mcp-code-smells.yml +0 -0
- {mcpsec-2.7.2 → mcpsec-2.7.3}/mcpsec/rules/mcp-command-injection.yml +0 -0
- {mcpsec-2.7.2 → mcpsec-2.7.3}/mcpsec/rules/mcp-dangerous-defaults.yml +0 -0
- {mcpsec-2.7.2 → mcpsec-2.7.3}/mcpsec/rules/mcp-dangerous-eval.yml +0 -0
- {mcpsec-2.7.2 → mcpsec-2.7.3}/mcpsec/rules/mcp-description-injection.yml +0 -0
- {mcpsec-2.7.2 → mcpsec-2.7.3}/mcpsec/rules/mcp-deserialization.yml +0 -0
- {mcpsec-2.7.2 → mcpsec-2.7.3}/mcpsec/rules/mcp-dotnet-injection.yml +0 -0
- {mcpsec-2.7.2 → mcpsec-2.7.3}/mcpsec/rules/mcp-go-injection.yml +0 -0
- {mcpsec-2.7.2 → mcpsec-2.7.3}/mcpsec/rules/mcp-indirect-injection.yml +0 -0
- {mcpsec-2.7.2 → mcpsec-2.7.3}/mcpsec/rules/mcp-path-traversal.yml +0 -0
- {mcpsec-2.7.2 → mcpsec-2.7.3}/mcpsec/rules/mcp-protocol-handler.yml +0 -0
- {mcpsec-2.7.2 → mcpsec-2.7.3}/mcpsec/rules/mcp-python-async.yml +0 -0
- {mcpsec-2.7.2 → mcpsec-2.7.3}/mcpsec/rules/mcp-python-broad.yml +0 -0
- {mcpsec-2.7.2 → mcpsec-2.7.3}/mcpsec/rules/mcp-python-injection.yml +0 -0
- {mcpsec-2.7.2 → mcpsec-2.7.3}/mcpsec/rules/mcp-resource-uri.yml +0 -0
- {mcpsec-2.7.2 → mcpsec-2.7.3}/mcpsec/rules/mcp-rust-injection.yml +0 -0
- {mcpsec-2.7.2 → mcpsec-2.7.3}/mcpsec/rules/mcp-secrets-exposure.yml +0 -0
- {mcpsec-2.7.2 → mcpsec-2.7.3}/mcpsec/rules/mcp-secrets-hardcoded.yml +0 -0
- {mcpsec-2.7.2 → mcpsec-2.7.3}/mcpsec/rules/mcp-server-patterns.yml +0 -0
- {mcpsec-2.7.2 → mcpsec-2.7.3}/mcpsec/rules/mcp-sql-injection.yml +0 -0
- {mcpsec-2.7.2 → mcpsec-2.7.3}/mcpsec/rules/mcp-ssrf-patterns.yml +0 -0
- {mcpsec-2.7.2 → mcpsec-2.7.3}/mcpsec/rules/mcp-taint-sinks.yml +0 -0
- {mcpsec-2.7.2 → mcpsec-2.7.3}/mcpsec/scanners/__init__.py +0 -0
- {mcpsec-2.7.2 → mcpsec-2.7.3}/mcpsec/scanners/annotation_integrity.py +0 -0
- {mcpsec-2.7.2 → mcpsec-2.7.3}/mcpsec/scanners/auth_audit.py +0 -0
- {mcpsec-2.7.2 → mcpsec-2.7.3}/mcpsec/scanners/base.py +0 -0
- {mcpsec-2.7.2 → mcpsec-2.7.3}/mcpsec/scanners/capability_escalation.py +0 -0
- {mcpsec-2.7.2 → mcpsec-2.7.3}/mcpsec/scanners/code_execution.py +0 -0
- {mcpsec-2.7.2 → mcpsec-2.7.3}/mcpsec/scanners/command_injection.py +0 -0
- {mcpsec-2.7.2 → mcpsec-2.7.3}/mcpsec/scanners/description_injection.py +0 -0
- {mcpsec-2.7.2 → mcpsec-2.7.3}/mcpsec/scanners/description_prompt_injection.py +0 -0
- {mcpsec-2.7.2 → mcpsec-2.7.3}/mcpsec/scanners/deserialization.py +0 -0
- {mcpsec-2.7.2 → mcpsec-2.7.3}/mcpsec/scanners/idor.py +0 -0
- {mcpsec-2.7.2 → mcpsec-2.7.3}/mcpsec/scanners/info_leak.py +0 -0
- {mcpsec-2.7.2 → mcpsec-2.7.3}/mcpsec/scanners/path_traversal.py +0 -0
- {mcpsec-2.7.2 → mcpsec-2.7.3}/mcpsec/scanners/prompt_injection.py +0 -0
- {mcpsec-2.7.2 → mcpsec-2.7.3}/mcpsec/scanners/rag_poisoning.py +0 -0
- {mcpsec-2.7.2 → mcpsec-2.7.3}/mcpsec/scanners/resource_ssrf.py +0 -0
- {mcpsec-2.7.2 → mcpsec-2.7.3}/mcpsec/scanners/response_classifier.py +0 -0
- {mcpsec-2.7.2 → mcpsec-2.7.3}/mcpsec/scanners/secrets_exposure.py +0 -0
- {mcpsec-2.7.2 → mcpsec-2.7.3}/mcpsec/scanners/sql_rce.py +0 -0
- {mcpsec-2.7.2 → mcpsec-2.7.3}/mcpsec/scanners/ssrf.py +0 -0
- {mcpsec-2.7.2 → mcpsec-2.7.3}/mcpsec/scanners/template_injection.py +0 -0
- {mcpsec-2.7.2 → mcpsec-2.7.3}/mcpsec/scanners/toolchain_analyzer.py +0 -0
- {mcpsec-2.7.2 → mcpsec-2.7.3}/mcpsec/sql_scanner/__init__.py +0 -0
- {mcpsec-2.7.2 → mcpsec-2.7.3}/mcpsec/sql_scanner/confirmer.py +0 -0
- {mcpsec-2.7.2 → mcpsec-2.7.3}/mcpsec/sql_scanner/detector.py +0 -0
- {mcpsec-2.7.2 → mcpsec-2.7.3}/mcpsec/sql_scanner/fingerprint.py +0 -0
- {mcpsec-2.7.2 → mcpsec-2.7.3}/mcpsec/sql_scanner/payloads.py +0 -0
- {mcpsec-2.7.2 → mcpsec-2.7.3}/mcpsec/static/__init__.py +0 -0
- {mcpsec-2.7.2 → mcpsec-2.7.3}/mcpsec/static/analysis/__init__.py +0 -0
- {mcpsec-2.7.2 → mcpsec-2.7.3}/mcpsec/static/analysis/reachability.py +0 -0
- {mcpsec-2.7.2 → mcpsec-2.7.3}/mcpsec/static/analysis/sink_scanner.py +0 -0
- {mcpsec-2.7.2 → mcpsec-2.7.3}/mcpsec/static/audit_engine.py +0 -0
- {mcpsec-2.7.2 → mcpsec-2.7.3}/mcpsec/static/framework/__init__.py +0 -0
- {mcpsec-2.7.2 → mcpsec-2.7.3}/mcpsec/static/framework/detector.py +0 -0
- {mcpsec-2.7.2 → mcpsec-2.7.3}/mcpsec/static/patterns/__init__.py +0 -0
- {mcpsec-2.7.2 → mcpsec-2.7.3}/mcpsec/static/patterns/base.py +0 -0
- {mcpsec-2.7.2 → mcpsec-2.7.3}/mcpsec/static/patterns/generator.py +0 -0
- {mcpsec-2.7.2 → mcpsec-2.7.3}/mcpsec/static/patterns/registry.py +0 -0
- {mcpsec-2.7.2 → mcpsec-2.7.3}/mcpsec/static/patterns/sanitizers/__init__.py +0 -0
- {mcpsec-2.7.2 → mcpsec-2.7.3}/mcpsec/static/patterns/sanitizers/sanitizers.py +0 -0
- {mcpsec-2.7.2 → mcpsec-2.7.3}/mcpsec/static/patterns/sinks/__init__.py +0 -0
- {mcpsec-2.7.2 → mcpsec-2.7.3}/mcpsec/static/patterns/sinks/code_execution.py +0 -0
- {mcpsec-2.7.2 → mcpsec-2.7.3}/mcpsec/static/patterns/sinks/command_injection.py +0 -0
- {mcpsec-2.7.2 → mcpsec-2.7.3}/mcpsec/static/patterns/sinks/crypto.py +0 -0
- {mcpsec-2.7.2 → mcpsec-2.7.3}/mcpsec/static/patterns/sinks/deserialization.py +0 -0
- {mcpsec-2.7.2 → mcpsec-2.7.3}/mcpsec/static/patterns/sinks/misc.py +0 -0
- {mcpsec-2.7.2 → mcpsec-2.7.3}/mcpsec/static/patterns/sinks/path_traversal.py +0 -0
- {mcpsec-2.7.2 → mcpsec-2.7.3}/mcpsec/static/patterns/sinks/sql_injection.py +0 -0
- {mcpsec-2.7.2 → mcpsec-2.7.3}/mcpsec/static/patterns/sinks/ssrf.py +0 -0
- {mcpsec-2.7.2 → mcpsec-2.7.3}/mcpsec/static/patterns/sinks/xxe.py +0 -0
- {mcpsec-2.7.2 → mcpsec-2.7.3}/mcpsec/static/patterns/sources/__init__.py +0 -0
- {mcpsec-2.7.2 → mcpsec-2.7.3}/mcpsec/static/patterns/sources/mcp_frameworks.py +0 -0
- {mcpsec-2.7.2 → mcpsec-2.7.3}/mcpsec/static/semgrep_engine.py +0 -0
- {mcpsec-2.7.2 → mcpsec-2.7.3}/mcpsec/static/source_fetcher.py +0 -0
- {mcpsec-2.7.2 → mcpsec-2.7.3}/mcpsec/ui.py +0 -0
- {mcpsec-2.7.2 → mcpsec-2.7.3}/tests/__init__.py +0 -0
- {mcpsec-2.7.2 → mcpsec-2.7.3}/tests/servers/__init__.py +0 -0
- {mcpsec-2.7.2 → mcpsec-2.7.3}/tests/servers/noisy_test_server.py +0 -0
- {mcpsec-2.7.2 → mcpsec-2.7.3}/tests/servers/stateful_test_server.py +0 -0
- {mcpsec-2.7.2 → mcpsec-2.7.3}/tests/test_config.py +0 -0
- {mcpsec-2.7.2 → mcpsec-2.7.3}/tests/test_exploit.py +0 -0
- {mcpsec-2.7.2 → mcpsec-2.7.3}/tests/test_fuzzer.py +0 -0
- {mcpsec-2.7.2 → mcpsec-2.7.3}/tests/test_sarif.py +0 -0
- {mcpsec-2.7.2 → mcpsec-2.7.3}/tests/test_scanners.py +0 -0
- {mcpsec-2.7.2 → mcpsec-2.7.3}/tests/test_semgrep_rules.py +0 -0
|
@@ -5,20 +5,26 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
-
## [2.7.2] - 2026-07-12
|
|
9
8
|
|
|
10
|
-
|
|
11
|
-
- **Codebase Cleanup & Deduplication**: Removed 156 duplicate regexes and consolidated 3,459 sink patterns into 9 organized files by vulnerability type.
|
|
12
|
-
- **Generator Consolidation**: Merged protocol_state generators and renamed memory_exhaustion_v2 for better maintainability.
|
|
13
|
-
- **Dead Code Removal**: Removed unused AI analyzer stubs ( aint_analyzer, py_analyzer) and SQL exploiter stubs.
|
|
14
|
-
- **Test Organization**: Moved test server fixtures from ests/ to ests/servers/ for cleaner separation of tests and fixtures.
|
|
9
|
+
## [2.7.3] - 2026-11-01
|
|
15
10
|
|
|
16
|
-
###
|
|
17
|
-
- **
|
|
18
|
-
-
|
|
19
|
-
-
|
|
11
|
+
### Added
|
|
12
|
+
- **Schema-Aware Argument Fuzzer (schema_fuzzer)**: Per-parameter, type-targeted mutation fuzzing driven by tool inputSchema.
|
|
13
|
+
- Type-targeted mutation catalog for strings (path traversal, command injection, template injection, SSRF, length abuse, unicode, format and pattern violations), numbers and integers (numeric boundaries, floats, NaN/Infinity tokens, min/max bounds), booleans (type confusion), arrays (item recursion, min/max bounds, deep nesting), and objects (prototype pollution, unexpected keys, deep nesting).
|
|
14
|
+
- Out-of-enum value generation for enum properties.
|
|
15
|
+
- Baseline value generation for non-targeted required parameters to ensure valid payloads reach tool handlers instead of failing schema validation.
|
|
16
|
+
- Schema-less tool support (defaults to untyped string argument fuzzing).
|
|
17
|
+
- Configurable intensity scaling (low, medium, high, insane).
|
|
18
|
+
- Comprehensive unit test suite in ests/test_schema_fuzzer.py.
|
|
20
19
|
|
|
20
|
+
### Improved
|
|
21
|
+
- **Fuzz Engine Wiring**: Automatically discovers server tools on startup and executes targeted schema fuzzing.
|
|
22
|
+
- Replaced naive single-value spray in uzz_engine.py with schema-aware parameter targeting and baseline parameter population.
|
|
23
|
+
|
|
24
|
+
## [2.7.2] - 2026-07-28
|
|
21
25
|
|
|
26
|
+
### Added
|
|
27
|
+
- Support for MCP 2026-07-28 stateless protocol lifecycle (server/discover).
|
|
22
28
|
## [2.7.1] - 2026-04-15
|
|
23
29
|
|
|
24
30
|
### Improved
|
|
@@ -315,4 +321,4 @@ mcpsec evolve --stdio "server" --corpus ./corpus --crashes ./crashes
|
|
|
315
321
|
- Protocol fuzzer with 500+ test cases
|
|
316
322
|
- AI-powered payload generation
|
|
317
323
|
- Semgrep-based static analysis
|
|
318
|
-
- Support for OpenAI, Anthropic, Google, Groq, DeepSeek, Ollama
|
|
324
|
+
- Support for OpenAI, Anthropic, Google, Groq, DeepSeek, Ollama
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: mcpsec
|
|
3
|
-
Version: 2.7.
|
|
3
|
+
Version: 2.7.3
|
|
4
4
|
Summary: Security scanner for MCP (Model Context Protocol) servers - pentest your AI agent's tool connections
|
|
5
5
|
Project-URL: Homepage, https://github.com/manthanghasadiya/mcpsec
|
|
6
6
|
Project-URL: Documentation, https://github.com/manthanghasadiya/mcpsec#readme
|
|
@@ -14,7 +14,11 @@ from typing import Any
|
|
|
14
14
|
|
|
15
15
|
from mcp import ClientSession, StdioServerParameters
|
|
16
16
|
from mcp.client.stdio import stdio_client
|
|
17
|
-
|
|
17
|
+
|
|
18
|
+
try:
|
|
19
|
+
from mcp.client.streamable_http import streamable_http_client
|
|
20
|
+
except ImportError: # mcp < 2.0.0
|
|
21
|
+
from mcp.client.streamable_http import streamablehttp_client as streamable_http_client
|
|
18
22
|
|
|
19
23
|
from mcpsec.models import (
|
|
20
24
|
PromptInfo,
|
|
@@ -77,21 +81,42 @@ class MCPSecClient:
|
|
|
77
81
|
self.session = await self._exit_stack.enter_async_context(
|
|
78
82
|
ClientSession(read_stream, write_stream)
|
|
79
83
|
)
|
|
80
|
-
await self.
|
|
84
|
+
await self._initialize()
|
|
81
85
|
return await self._enumerate()
|
|
82
86
|
|
|
83
87
|
async def connect_http(self, url: str, headers: dict | None = None) -> ServerProfile:
|
|
84
88
|
"""Connect to an MCP server via streamable HTTP transport."""
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
89
|
+
try:
|
|
90
|
+
from mcp.client.streamable_http import create_mcp_http_client
|
|
91
|
+
|
|
92
|
+
http_client = create_mcp_http_client(headers=headers or {})
|
|
93
|
+
except ImportError: # mcp < 2.0.0
|
|
94
|
+
http_client = None
|
|
95
|
+
|
|
96
|
+
if http_client is not None:
|
|
97
|
+
http_transport = await self._exit_stack.enter_async_context(
|
|
98
|
+
streamable_http_client(url=url, http_client=http_client)
|
|
99
|
+
)
|
|
100
|
+
else:
|
|
101
|
+
http_transport = await self._exit_stack.enter_async_context(
|
|
102
|
+
streamable_http_client(url=url, headers=headers)
|
|
103
|
+
)
|
|
88
104
|
read_stream, write_stream = http_transport[0], http_transport[1]
|
|
89
105
|
self.session = await self._exit_stack.enter_async_context(
|
|
90
106
|
ClientSession(read_stream, write_stream)
|
|
91
107
|
)
|
|
92
|
-
await self.
|
|
108
|
+
await self._initialize()
|
|
93
109
|
return await self._enumerate()
|
|
94
110
|
|
|
111
|
+
async def _initialize(self):
|
|
112
|
+
"""Initialize the session, preferring the stateless server/discover probe."""
|
|
113
|
+
if not self.session:
|
|
114
|
+
raise RuntimeError("Not connected")
|
|
115
|
+
try:
|
|
116
|
+
await self.session.discover()
|
|
117
|
+
except Exception:
|
|
118
|
+
await self.session.initialize()
|
|
119
|
+
|
|
95
120
|
async def call_tool(
|
|
96
121
|
self, tool_name: str, arguments: dict[str, Any], timeout: float = 30.0
|
|
97
122
|
) -> Any:
|
|
@@ -124,9 +149,12 @@ class MCPSecClient:
|
|
|
124
149
|
for tool in tools_result.tools:
|
|
125
150
|
params = {}
|
|
126
151
|
raw_schema = {}
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
152
|
+
input_schema = getattr(tool, "inputSchema", None) or getattr(
|
|
153
|
+
tool, "input_schema", None
|
|
154
|
+
)
|
|
155
|
+
if input_schema and "properties" in input_schema:
|
|
156
|
+
raw_schema = input_schema
|
|
157
|
+
for param_name, param_def in input_schema["properties"].items():
|
|
130
158
|
param_type = param_def.get("type", "any")
|
|
131
159
|
params[param_name] = param_type
|
|
132
160
|
|
|
@@ -89,16 +89,23 @@ def main():
|
|
|
89
89
|
proc = subprocess.Popen(SERVER_CMD.split(), stdin=subprocess.PIPE,
|
|
90
90
|
stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
|
|
91
91
|
|
|
92
|
-
#
|
|
93
|
-
print("[*]
|
|
94
|
-
send_jsonrpc(proc, {{
|
|
95
|
-
"jsonrpc": "2.0", "id": 1, "method": "
|
|
96
|
-
"params": {{"protocolVersion": "
|
|
92
|
+
# Probe: server/discover (2026-07-28 stateless) with legacy initialize fallback
|
|
93
|
+
print("[*] Probing server capabilities...")
|
|
94
|
+
discover = send_jsonrpc(proc, {{
|
|
95
|
+
"jsonrpc": "2.0", "id": 1, "method": "server/discover",
|
|
96
|
+
"params": {{"protocolVersion": "2026-07-28", "capabilities": {{}},
|
|
97
97
|
"clientInfo": {{"name": "mcpsec-poc", "version": "1.0"}}}}
|
|
98
98
|
}})
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
99
|
+
if not discover or "result" not in discover:
|
|
100
|
+
print("[*] server/discover unsupported, falling back to legacy initialize...")
|
|
101
|
+
send_jsonrpc(proc, {{
|
|
102
|
+
"jsonrpc": "2.0", "id": 1, "method": "initialize",
|
|
103
|
+
"params": {{"protocolVersion": "2024-11-05", "capabilities": {{}},
|
|
104
|
+
"clientInfo": {{"name": "mcpsec-poc", "version": "1.0"}}}}
|
|
105
|
+
}})
|
|
106
|
+
proc.stdin.write(json.dumps({{"jsonrpc":"2.0","method":"notifications/initialized"}}) + "\\n")
|
|
107
|
+
proc.stdin.flush()
|
|
108
|
+
time.sleep(0.5)
|
|
102
109
|
|
|
103
110
|
# Exploit Request
|
|
104
111
|
print("[*] Sending exploit payload to {tool_name}...")
|
|
@@ -264,18 +264,18 @@ class ChainEngine:
|
|
|
264
264
|
if hasattr(self._transport, "framing"):
|
|
265
265
|
self._transport.framing = "jsonl"
|
|
266
266
|
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
"
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
267
|
+
init_response = await self._transport.send_request_async(
|
|
268
|
+
{
|
|
269
|
+
"jsonrpc": "2.0",
|
|
270
|
+
"id": 1,
|
|
271
|
+
"method": "server/discover",
|
|
272
|
+
"params": {
|
|
273
|
+
"protocolVersion": "2026-07-28",
|
|
274
|
+
"capabilities": {},
|
|
275
|
+
"clientInfo": {"name": "mcpsec-chained", "version": "1.0.0"},
|
|
276
|
+
},
|
|
277
|
+
}
|
|
278
|
+
)
|
|
279
279
|
|
|
280
280
|
# If JSONL didn't work, try CRLF framing (Node servers)
|
|
281
281
|
if not init_response or "result" not in init_response:
|
|
@@ -295,8 +295,54 @@ class ChainEngine:
|
|
|
295
295
|
|
|
296
296
|
if hasattr(self._transport, "framing"):
|
|
297
297
|
self._transport.framing = "crlf"
|
|
298
|
+
init_response = await self._transport.send_request_async(
|
|
299
|
+
{
|
|
300
|
+
"jsonrpc": "2.0",
|
|
301
|
+
"id": 1,
|
|
302
|
+
"method": "server/discover",
|
|
303
|
+
"params": {
|
|
304
|
+
"protocolVersion": "2026-07-28",
|
|
305
|
+
"capabilities": {},
|
|
306
|
+
"clientInfo": {"name": "mcpsec-chained", "version": "1.0.0"},
|
|
307
|
+
},
|
|
308
|
+
}
|
|
309
|
+
)
|
|
310
|
+
|
|
311
|
+
# Legacy fallback: initialize handshake
|
|
312
|
+
if not init_response or "result" not in init_response:
|
|
313
|
+
if self.config.verbose:
|
|
314
|
+
console.print(" [dim]server/discover failed, trying legacy initialize...[/dim]")
|
|
315
|
+
|
|
316
|
+
if hasattr(self._transport, "restart_async"):
|
|
317
|
+
await self._transport.restart_async()
|
|
318
|
+
else:
|
|
319
|
+
restart_fn = getattr(self._transport, "restart", None)
|
|
320
|
+
if restart_fn:
|
|
321
|
+
await asyncio.to_thread(restart_fn)
|
|
322
|
+
await asyncio.sleep(1.0)
|
|
323
|
+
|
|
324
|
+
init_msg = {
|
|
325
|
+
"jsonrpc": "2.0",
|
|
326
|
+
"id": 1,
|
|
327
|
+
"method": "initialize",
|
|
328
|
+
"params": {
|
|
329
|
+
"protocolVersion": "2024-11-05",
|
|
330
|
+
"capabilities": {},
|
|
331
|
+
"clientInfo": {"name": "mcpsec-chained", "version": "1.0.0"},
|
|
332
|
+
},
|
|
333
|
+
}
|
|
298
334
|
init_response = await self._transport.send_request_async(init_msg)
|
|
299
335
|
|
|
336
|
+
if init_response and "result" in init_response:
|
|
337
|
+
# Send initialized notification (no ID!)
|
|
338
|
+
await self._transport.send_notification_async(
|
|
339
|
+
{
|
|
340
|
+
"jsonrpc": "2.0",
|
|
341
|
+
"method": "notifications/initialized",
|
|
342
|
+
}
|
|
343
|
+
)
|
|
344
|
+
await asyncio.sleep(0.5)
|
|
345
|
+
|
|
300
346
|
if not init_response or "result" not in init_response:
|
|
301
347
|
console.print("[yellow] Warning: Server did not respond to initialize[/yellow]")
|
|
302
348
|
return
|
|
@@ -305,16 +351,6 @@ class ChainEngine:
|
|
|
305
351
|
framing = getattr(self._transport, "framing", "unknown")
|
|
306
352
|
console.print(f" [dim]Using framing: {framing}[/dim]")
|
|
307
353
|
|
|
308
|
-
# Send initialized notification (no ID!)
|
|
309
|
-
await self._transport.send_notification_async(
|
|
310
|
-
{
|
|
311
|
-
"jsonrpc": "2.0",
|
|
312
|
-
"method": "notifications/initialized",
|
|
313
|
-
}
|
|
314
|
-
)
|
|
315
|
-
|
|
316
|
-
await asyncio.sleep(0.5) # Give server time to process
|
|
317
|
-
|
|
318
354
|
# Get tools list
|
|
319
355
|
tools_response = await self._transport.send_request_async(
|
|
320
356
|
{"jsonrpc": "2.0", "id": 2, "method": "tools/list", "params": {}}
|
|
@@ -803,25 +839,38 @@ class ChainEngine:
|
|
|
803
839
|
await self._transport.restart()
|
|
804
840
|
await asyncio.sleep(1.0)
|
|
805
841
|
|
|
806
|
-
# Re-initialize
|
|
807
|
-
await self._transport.send_request_async(
|
|
842
|
+
# Re-initialize (discover first, legacy fallback)
|
|
843
|
+
init_response = await self._transport.send_request_async(
|
|
808
844
|
{
|
|
809
845
|
"jsonrpc": "2.0",
|
|
810
846
|
"id": 1,
|
|
811
|
-
"method": "
|
|
847
|
+
"method": "server/discover",
|
|
812
848
|
"params": {
|
|
813
|
-
"protocolVersion": "
|
|
849
|
+
"protocolVersion": "2026-07-28",
|
|
814
850
|
"capabilities": {},
|
|
815
851
|
"clientInfo": {"name": "mcpsec-chained", "version": "1.0.0"},
|
|
816
852
|
},
|
|
817
853
|
}
|
|
818
854
|
)
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
855
|
+
if not init_response or "result" not in init_response:
|
|
856
|
+
await self._transport.send_request_async(
|
|
857
|
+
{
|
|
858
|
+
"jsonrpc": "2.0",
|
|
859
|
+
"id": 1,
|
|
860
|
+
"method": "initialize",
|
|
861
|
+
"params": {
|
|
862
|
+
"protocolVersion": "2024-11-05",
|
|
863
|
+
"capabilities": {},
|
|
864
|
+
"clientInfo": {"name": "mcpsec-chained", "version": "1.0.0"},
|
|
865
|
+
},
|
|
866
|
+
}
|
|
867
|
+
)
|
|
868
|
+
await self._transport.send_notification_async(
|
|
869
|
+
{
|
|
870
|
+
"jsonrpc": "2.0",
|
|
871
|
+
"method": "notifications/initialized",
|
|
872
|
+
}
|
|
873
|
+
)
|
|
825
874
|
await asyncio.sleep(0.5)
|
|
826
875
|
|
|
827
876
|
def _print_dependency_graph(self, graph: dict) -> None:
|
|
@@ -182,8 +182,8 @@ class EvolveFuzzEngine:
|
|
|
182
182
|
def _create_default_seeds(self):
|
|
183
183
|
"""Create initial seed inputs."""
|
|
184
184
|
seeds = [
|
|
185
|
-
{"jsonrpc": "2.0", "id": 1, "method": "
|
|
186
|
-
"protocolVersion": "
|
|
185
|
+
{"jsonrpc": "2.0", "id": 1, "method": "server/discover", "params": {
|
|
186
|
+
"protocolVersion": "2026-07-28",
|
|
187
187
|
"capabilities": {},
|
|
188
188
|
"clientInfo": {"name": "mcpsec-evolve", "version": "1.0"}
|
|
189
189
|
}},
|
|
@@ -194,6 +194,9 @@ class EvolveFuzzEngine:
|
|
|
194
194
|
{"jsonrpc": "2.0", "id": 6, "method": "tools/call", "params": {
|
|
195
195
|
"name": "test", "arguments": {}
|
|
196
196
|
}},
|
|
197
|
+
{"jsonrpc": "2.0", "id": 7, "method": "tasks/get", "params": {
|
|
198
|
+
"taskId": "task-1"
|
|
199
|
+
}},
|
|
197
200
|
]
|
|
198
201
|
|
|
199
202
|
for seed in seeds:
|
|
@@ -212,7 +215,22 @@ class EvolveFuzzEngine:
|
|
|
212
215
|
try:
|
|
213
216
|
self.process = await self._start_server()
|
|
214
217
|
|
|
215
|
-
# Send initialize
|
|
218
|
+
# Send server/discover (2026-07-28 stateless), fall back to initialize
|
|
219
|
+
discover_req = json.dumps({
|
|
220
|
+
"jsonrpc": "2.0", "id": 1, "method": "server/discover",
|
|
221
|
+
"params": {
|
|
222
|
+
"protocolVersion": "2026-07-28",
|
|
223
|
+
"capabilities": {},
|
|
224
|
+
"clientInfo": {"name": "mcpsec-evolve", "version": "1.0"}
|
|
225
|
+
}
|
|
226
|
+
}).encode() + b"\n"
|
|
227
|
+
|
|
228
|
+
self.process.stdin.write(discover_req)
|
|
229
|
+
self.process.stdin.flush()
|
|
230
|
+
|
|
231
|
+
await asyncio.sleep(0.5)
|
|
232
|
+
|
|
233
|
+
# Legacy fallback: initialize handshake
|
|
216
234
|
init_req = json.dumps({
|
|
217
235
|
"jsonrpc": "2.0", "id": 1, "method": "initialize",
|
|
218
236
|
"params": {
|
|
@@ -69,7 +69,8 @@ class MCPStructureMutator(Mutator):
|
|
|
69
69
|
"""Change the method to something unexpected."""
|
|
70
70
|
methods = [
|
|
71
71
|
"tools/call", "tools/list", "resources/read", "resources/list",
|
|
72
|
-
"prompts/get", "initialize", "
|
|
72
|
+
"prompts/get", "initialize", "server/discover", "tasks/get",
|
|
73
|
+
"tasks/update", "subscriptions/listen", "ping", "shutdown",
|
|
73
74
|
"../../../etc/passwd", "'; DROP TABLE tools;--",
|
|
74
75
|
"AAAA" * 100, "", None, 12345, {"nested": "method"},
|
|
75
76
|
"tools/call\x00hidden", "tools/../../call",
|
|
@@ -213,6 +213,8 @@ class DictionaryMutator(Mutator):
|
|
|
213
213
|
b'"initialize"', b'"tools/list"', b'"tools/call"',
|
|
214
214
|
b'"resources/read"', b'"resources/list"', b'"prompts/list"',
|
|
215
215
|
b'"notifications/initialized"', b'"ping"',
|
|
216
|
+
b'"server/discover"', b'"tasks/get"', b'"tasks/update"',
|
|
217
|
+
b'"subscriptions/listen"', b'"inputResponses"', b'"input_required"',
|
|
216
218
|
b'"protocolVersion"', b'"capabilities"', b'"clientInfo"',
|
|
217
219
|
b'"name"', b'"version"', b'"arguments"',
|
|
218
220
|
b'null', b'true', b'false', b'{}', b'[]',
|