Skip to content

OWASP MCP Top 10 coverage

Spec: https://owasp.org/www-project-mcp-top-10/ (beta) Last verified (global): 2026-11-10

The evidence behind the README's MCP Top-10 table. Full: the controls that run on the @Airlock call path cover the risk where it reaches a tool call, and nothing the risk names is out of scope. Partial: part of the risk is out of scope, or the control that covers it runs only where the host calls it. No benchmark in this repository maps its corpus to MCP01-MCP10, so no row has a measured block-rate.

Risks

Risk ID Risk Coverage Why Last verified Measured block-rate
MCP01 Token Mismanagement & Secret Exposure Partial On the call path, ArgumentSecretGuard refuses secrets in arguments when a policy sets it, and the sanitizer masks the secrets its detectors find in output; MCPProxyGuard runs only where a proxy calls it. Hard-coded credentials, token lifetime, and secrets the host keeps in model memory or its own logs are out of scope. 2026-11-10 not measured
MCP02 Privilege Escalation via Scope Creep Partial Agent identity (step 2) and capability gating (step 4) run on every call. CredentialScope runs only where the host calls MCPProxyGuard.validate_tool_credentials(), and the union boundary only at CapabilityCapEngine.grant_lease(). The scopes an authorization server grants a server, and how they grow, are set outside the call boundary. Was Full until 2026-11-10. 2026-11-10 not measured
MCP03 Tool Poisoning Partial Ghost-argument rejection, the tool-name deny list and SafePath / SafeURL check each call. The description-manifest guard runs only where the host checks a tool at admission, and compares declared arguments and side effects, not instructions written into a description. A poisoned description that steers the model into well-formed calls to allowed tools passes all four; the MCPTox replay counts how often. Was Full until 2026-11-10. 2026-11-10 not measured
MCP04 Software Supply Chain Attacks & Dependency Tampering Partial The tool-name deny list refuses download_plugin_* / fetch_plugin_* calls on the call path. The Ox stdio defaults screen a server's launch command only where the host calls validate_stdio_command() before spawning it. A tampered package or dependency inside an MCP server is out of scope. Was Full until 2026-11-10. 2026-11-10 not measured
MCP05 Command Injection & Execution Full Untrusted input reaches a command through a tool argument or a server's launch command, and a control covers each. On the call path, ToolArgSinkGuard (step 2.75) and ShellCommandPolicy (step 2.77) screen arguments when a policy sets them, the tool-name deny list (step 2) refuses shell-shaped tools, and sandbox=True runs the tool in E2B. The stdio guard screens launch commands and CodeSandboxBridgeGuard is a second argument screen; the host calls both. 2026-11-10 not measured
MCP06 Intent Flow Subversion Partial ToolOutputTrustGuard checks each result after the tool runs when the config sets one, and @requires_human_oversight holds a decorated tool for an approver. Instructions the output screen does not flag, and prompts that reach the model outside a tool result, are out of scope. 2026-11-10 not measured
MCP07 Insufficient Authentication & Authorization Partial Every listed control is a helper or preset the host calls at an OAuth or MCP protocol step (authorization, discovery, request routing, step-up, Tasks); the @Airlock wrapper calls none of them. Transport-level authentication stays with the MCP server. 2026-11-10 not measured
MCP08 Lack of Audit and Telemetry Partial The @Airlock wrapper writes a JSON Lines record for every call it handles, refused calls included (on by default), and a hash-chained decision record when the config sets a DecisionLog. OpenTelemetry export and spans and metrics run only where the host wires them; the wrapper emits neither. Context changes and model interactions outside a tool call are not recorded. Was Full until 2026-11-10. 2026-11-10 not measured
MCP09 Shadow MCP Servers Partial Attested admission and the LAN guard check a server the host routes through them, before its tools are used. Finding an MCP server deployed outside governance, which never reaches these checks, is out of scope. 2026-11-10 not measured
MCP10 Context Injection & Over-Sharing Partial The sanitizer masks the PII and secrets its detectors find in each result (on by default), and ToolOutputTrustGuard envelopes flagged output when the config sets one. The workspace-scoped WorkspacePIIConfig applies only through sanitize_with_workspace_config(), which the host calls. Context the host shares or keeps across tasks, users or agents, and sensitive data no detector matches, are out of scope. Was Full until 2026-11-10. 2026-11-10 not measured

Controls

Each control is named as the README names it. Runs: an @Airlock step (the # Step N comments in core.py), @Airlock execute, post-execution or wrapper (the decision record, written around the steps), a decorator of its own, or host-called: a helper the host calls itself, which the @Airlock wrapper never calls.

Risk ID Control Runs Module Preset Test
MCP01 MCPProxyGuard rejects passthrough headers and enforces audience host-called: MCPProxyGuard.validate_request(), in an MCP proxy agent_airlock.mcp_proxy_guard — tests/test_mcp_proxy_guard.py
MCP01 ArgumentSecretGuard refuses secrets in arguments @Airlock step 2.76: when the policy sets argument_secret_guard agent_airlock.argument_secret_guard argument_secret_defaults tests/test_argument_secret_guard_0_10_29.py
MCP01 sanitizer masks secrets in tool output @Airlock post-execution: mask_secrets, on by default agent_airlock.sanitizer — tests/test_sanitizer.py
MCP02 SecurityPolicy(require_agent_id=True) @Airlock step 2: refuses a call with no agent identity agent_airlock.policy owasp_mcp_top_10_2026_policy tests/test_policy_identity.py
MCP02 capability gating @Airlock step 4: the policy's CapabilityPolicy against the tool's @requires agent_airlock.capabilities owasp_mcp_top_10_2026_policy tests/test_capabilities.py
MCP02 CredentialScope host-called: MCPProxyGuard.validate_tool_credentials() agent_airlock.mcp_proxy_guard — tests/test_credential_scope.py
MCP02 runtime capability-union deny at grant time host-called: CapabilityCapEngine.grant_lease() agent_airlock.capability_caps.engine — tests/capability_caps/test_union.py
MCP03 Ghost-arg rejection @Airlock step 1: UnknownArgsMode.BLOCK agent_airlock.unknown_args — tests/test_unknown_args.py
MCP03 SafePath / SafeURL @Airlock execute: strict validation of arguments annotated with them agent_airlock.safe_types — tests/test_safe_types.py
MCP03 mcp_description_manifest_guard_defaults() host-called: the preset's check(description) when a tool is admitted agent_airlock.mcp_spec.description_manifest_guard mcp_description_manifest_guard_defaults tests/mcp_spec/test_description_manifest_guard.py
MCP03 install_* deny @Airlock step 2: denied_tools agent_airlock.policy owasp_mcp_top_10_2026_policy tests/test_policy_presets.py
MCP04 stdio_guard_ox_defaults() (Ox 2026-04-16 advisory) host-called: validate_stdio_command() before a server is spawned agent_airlock.mcp_spec.stdio_guard stdio_guard_ox_defaults tests/cves/test_ox_mcp_stdio.py
MCP04 download_plugin_* / fetch_plugin_* deny @Airlock step 2: denied_tools agent_airlock.policy owasp_mcp_top_10_2026_policy tests/test_policy_presets.py
MCP05 stdio_guard shell-metachar and deny-pattern rules host-called: validate_stdio_command() before a server is spawned agent_airlock.mcp_spec.stdio_guard stdio_guard_ox_defaults tests/cves/test_ox_mcp_stdio.py
MCP05 ToolArgSinkGuard @Airlock step 2.75: when the policy sets tool_arg_sink_guard agent_airlock.tool_arg_sink_guard tool_arg_sink_defaults tests/test_tool_arg_sink_guard.py
MCP05 ShellCommandPolicy on arguments @Airlock step 2.77: when the policy sets shell_command_policy agent_airlock.shell_command_policy shell_command_defaults tests/test_shell_command_policy_0_10_29.py
MCP05 exec_* / run_* / system_* deny @Airlock step 2: denied_tools agent_airlock.policy owasp_mcp_top_10_2026_policy tests/test_policy_presets.py
MCP05 CodeSandboxBridgeGuard for code-tool sandbox escapes host-called: the preset's guard.evaluate(args) agent_airlock.mcp_spec.code_sandbox_bridge_guard code_sandbox_bridge_defaults tests/mcp_spec/test_code_sandbox_bridge_guard.py
MCP05 E2B sandbox (sandbox=True) @Airlock execute: @Airlock(sandbox=True) runs the tool in E2B, and refuses the call when E2B is missing or fails agent_airlock.sandbox — tests/test_sandbox_correctness_0_10_17.py
MCP06 ToolOutputTrustGuard envelopes injected-instruction output @Airlock post-execution: when AirlockConfig(output_trust_guard=...) sets one agent_airlock.tool_output_trust_guard untrusted_tool_output_defaults tests/test_output_trust_guard_0_10_29.py, tests/test_tool_output_trust_guard.py
MCP06 @requires_human_oversight gates high-value actions own decorator: holds the decorated tool until an approver answers agent_airlock.oversight — tests/test_oversight.py
MCP07 OAuth 2.1 + PKCE S256 helpers (a redirect URI with a fragment is refused, RFC 6749 section 3.1.2) host-called: generate_pkce_challenge(), validate_redirect_uri() agent_airlock.mcp_spec.oauth — tests/mcp_spec/test_oauth.py
MCP07 iss mix-up validation host-called: validate_authorization_response_iss() agent_airlock.mcp_spec.oauth mcp_spec_2026_07_defaults tests/test_mcp_spec_2026_07_preset.py, tests/mcp_spec/test_oauth.py
MCP07 issuer and resource in discovered metadata bound to the URLs the client asked for (RFC 8414 and RFC 9728 section 3.3) host-called: validate_metadata_issuer(), validate_protected_resource() agent_airlock.mcp_spec.oauth — tests/mcp_spec/test_oauth.py
MCP07 header/body routing integrity host-called: the preset's check on each request agent_airlock.mcp_spec.header_integrity mcp_spec_2026_07_header_integrity_defaults tests/test_mcp_header_integrity_preset.py
MCP07 unsigned-_meta trust boundary host-called: the preset's check on each request's _meta agent_airlock.mcp_spec.meta_trust mcp_meta_trust_2026_07_defaults tests/test_mcp_meta_trust_preset.py
MCP07 step-up scope guard host-called: the preset's check at each step-up agent_airlock.mcp_spec.step_up_scope_guard mcp_step_up_scope_2026_07_defaults tests/test_mcp_step_up_scope_preset.py
MCP07 Tasks lifecycle and admission guards host-called: the presets' checks on each Tasks request agent_airlock.mcp_spec.tasks_lifecycle_guard, agent_airlock.mcp_spec.tasks_admission_guard mcp_tasks_lifecycle_2026_07_defaults, mcp_tasks_2026_07_28_defaults tests/test_mcp_tasks_lifecycle_preset.py, tests/test_mcp_tasks_2026_07_28_preset.py
MCP08 JSON-Lines audit log @Airlock post-execution: enable_audit_log, on by default; refused calls are recorded too agent_airlock.audit — tests/test_audit.py
MCP08 hash-chained decision log @Airlock wrapper: a decision entry before the tool runs and an outcome entry after, when AirlockConfig(decision_log=...) sets one agent_airlock.conformance.decision_log — tests/test_decision_record_0_10_29.py, tests/test_conformance_decision_log.py
MCP08 OpenTelemetry export host-called: OTelAuditExporter; the audit_otel_* config fields are never applied agent_airlock.audit_otel — tests/test_audit_otel.py
MCP08 spans and metrics host-called: observability.configure() and observe; the @Airlock wrapper emits none agent_airlock.observability — tests/test_new_features.py
MCP09 Attested tool-server admission host-called: admit_server_tool() or MCPProxyGuard.audit_tool_admission() agent_airlock.mcp_spec.attested_admission mcp_attested_admission_defaults tests/test_attested_admission.py
MCP09 LAN unauthenticated-MCP guard host-called: LANUnauthRCEGuard on a server's registration agent_airlock.mcp_spec.lan_unauth_rce_guard lan_unauth_mcp_guard tests/mcp_spec/test_lan_unauth_rce_guard.py
MCP10 PII and secret sanitizer @Airlock post-execution: mask_pii and mask_secrets, on by default agent_airlock.sanitizer — tests/test_sanitizer.py
MCP10 workspace-scoped config host-called: sanitize_with_workspace_config() with a WorkspacePIIConfig; the @Airlock sanitizer does not read it agent_airlock.sanitizer — tests/test_workspace_pii.py
MCP10 ToolOutputTrustGuard untrusted-data envelope @Airlock post-execution: when AirlockConfig(output_trust_guard=...) sets one agent_airlock.tool_output_trust_guard untrusted_tool_output_defaults tests/test_output_trust_guard_0_10_29.py