Security hardening: input validation and fail-closed permissions#153
Security hardening: input validation and fail-closed permissions#153galatanovidiu merged 5 commits intotrunkfrom
Conversation
Non-array arguments (string, integer) passed to tools/call or prompts/get now return a JSON-RPC invalid_params error instead of being silently passed to execution. Null and missing arguments continue to default to an empty array as before.
When a custom transport_permission_callback returns WP_Error or throws an exception, check_permission now returns false (fail-closed) instead of falling through to the weaker default capability check.
|
The following accounts have interacted with this PR and/or linked issues. I will continue to update these lists as activity occurs. You can also manually ask me to refresh this list by adding the If you're merging code through a pull request on GitHub, copy and paste the following into the bottom of the merge commit message. To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook. |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## trunk #153 +/- ##
============================================
+ Coverage 87.74% 87.76% +0.01%
- Complexity 1230 1234 +4
============================================
Files 54 54
Lines 3990 3996 +6
============================================
+ Hits 3501 3507 +6
Misses 489 489
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Pull request overview
This PR hardens the MCP adapter’s request handling by tightening input validation, making transport permissions fail closed, and normalizing pre-filter short-circuit error types to be consistent with the resource handler behavior.
Changes:
- Validate
argumentsis an array fortools/callandprompts/get, returninginvalid_paramswhen not. - Change
HttpTransport::check_permission()to deny access when the custom permission callback throws or returnsWP_Error(fail-closed), while logging the failure. - Normalize
mcp_adapter_pre_prompt_getshort-circuit behavior to returninternal_error(-32603) instead ofpermission_denied.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 7 comments.
Show a summary per file
| File | Description |
|---|---|
| includes/Handlers/Tools/ToolsHandler.php | Adds arguments type validation for tools/call. |
| includes/Handlers/Prompts/PromptsHandler.php | Adds arguments type validation and normalizes pre-filter WP_Error to internal_error. |
| includes/Transport/HttpTransport.php | Changes permission callback failure handling to fail closed and updates filter PHPDoc accordingly. |
| tests/Unit/Handlers/ToolsHandlerCallTest.php | Adds unit tests for invalid/non-array arguments and null/missing arguments. |
| tests/Unit/Handlers/PromptsHandlerTest.php | Updates pre-filter short-circuit assertions and adds unit tests for argument validation. |
| tests/Integration/HttpTransportTest.php | Updates integration coverage to assert fail-closed behavior and logging for permission callback failures. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
The custom transport_permission_callback could return any truthy/falsy value. Cast to (bool) so the return type matches the declared @return bool while preserving WordPress truthy/falsy semantics.
Aligns new test methods with the existing naming convention used throughout the test suite.
The invalid_params tests now assert -32602 error code in addition to the error message, protecting against regressions in McpErrorFactory behavior.
gziolo
left a comment
There was a problem hiding this comment.
All checks pass locally — 983 tests, PHPCS clean, PHPStan Level 8 clean.
The three fixes are correct and well-motivated:
- Arguments type validation — proper
isset() && !is_array()guard in both handlers, correct error code (-32602), null/missing arguments unaffected. - Fail-closed permissions —
WP_Errorand exception paths now deny access instead of silently falling through to the weaker default check. - Pre-filter error normalization — aligns
mcp_adapter_pre_prompt_getwithmcp_adapter_pre_resource_readbehavior.
Previously raised inconsistencies were properly addressed. Great refactor overall.
Why
Three gaps flagged during the v0.5.0 code review and PR #151 review:
argumentsparameter not type-checked.tools/callandprompts/getaccept whatever the client sends asarguments— a string, integer, or boolean passes silently into the execution layer. The MCP spec requiresargumentsto be an object (associative array in PHP).Permission callback failures fail open. When a custom
transport_permission_callbackreturnsWP_Erroror throws an exception,HttpTransport::check_permission()falls through to the weaker default capability check instead of denying access. A broken permission callback should not silently degrade security.Inconsistent pre-filter error types. The
mcp_adapter_pre_prompt_getfilter returnspermission_denied(-32008) onWP_Errorshort-circuit, whilemcp_adapter_pre_resource_readreturnsinternal_error(-32603). Pre-filter short-circuits represent generic blocks (rate limiting, maintenance mode, content policy), not permission failures. Fixes Normalize WP_Error short-circuit error types across pre-execution filters #152.What
Input validation (
ToolsHandler,PromptsHandler):is_array()check onargumentsbefore use in bothcall_tool()andget_prompt()invalid_params(-32602) with message"arguments must be an object"[]— no behavior change on the happy pathFail-closed permissions (
HttpTransport):check_permission()now returnsfalsewhen the custom callback returnsWP_Erroror throwsPre-filter error normalization (
PromptsHandler):mcp_adapter_pre_prompt_getWP_Error handling frompermission_denied()tointernal_error()mcp_adapter_pre_resource_readTest plan
WP_Errorand exception permission callback scenarios-32603error code on pre-filter short-circuit