Fixes #3925: Implemented the content_types field functionality for PluginCondition#4005
Conversation
## Summary Implemented the `content_types` field functionality for `PluginCondition` to enable plugins to execute conditionally based on HTTP Content-Type headers. ## Changes ### Core Implementation - **models.py**: Added `content_type: Optional[str]` field to `GlobalContext` - **utils.py**: - Added `normalize_content_type()` helper for case-insensitive matching with charset/parameter stripping - Updated `matches()` function to check `content_types` conditions - **auth.py**: Extract Content-Type from request headers (2 locations) - **tool_service.py**: Extract Content-Type from request headers (3 locations) - **http_auth_middleware.py**: Extract Content-Type from request headers (1 location) - **rbac.py**: Extract Content-Type from request headers (1 location) ### Testing - **test_content_type_matching.py**: Added 21 comprehensive unit tests covering: - Exact matching, case-insensitive matching, charset parameter stripping - Multiple content types (OR logic), combined conditions (AND logic) - Permissive defaults (None content_type, empty conditions) - Edge cases (empty strings, whitespace, malformed headers) ### Documentation - **plugins.md**: Added "Content Type Filtering" section with examples, security notes, and performance benefits - **index.md**: Enhanced "Conditional Execution" section with practical content_types examples Signed-off-by: Jitesh Nair <jiteshnair@ibm.com>
🔍 Comprehensive Code Review — PR #4005Fixes #3925: Implemented the ✅ Issue Coverage AssessmentAll 4 acceptance criteria met:
🚨 Required Changes Before Merge1. CRITICAL: Add Type Guard to Prevent CrashesFile: Issue: Function assumes string input but could receive None/int/other types, causing AttributeError. Fix Required: def normalize_content_type(content_type: str) -> str:
"""Extract base content type without parameters."""
if not isinstance(content_type, str): # ADD THIS
return '' # ADD THIS
return content_type.split(";")[0].strip().lower()Severity: Medium (crashes plugin evaluation) 2. CRITICAL: Add Input Validation to Prevent DoSFile: Issue: Content-Type headers stored without validation. Attacker could send 10MB header causing memory exhaustion. Fix Required: from pydantic import field_validator
class GlobalContext(BaseModel):
# ... existing fields ...
content_type: Optional[str] = None
@field_validator('content_type')
def validate_content_type(cls, v):
if v is None:
return v
if len(v) > 200:
raise ValueError("Content-Type header too long")
if not v.isprintable():
raise ValueError("Content-Type contains invalid characters")
return vSeverity: Medium (DoS potential, CWE-20) 3. HIGH: Performance Optimization RequiredFile: Issue: Content-types normalized on every request (1000s/sec). Creates unnecessary CPU overhead. Fix Required: # In PluginCondition model
@field_validator('content_types')
def normalize_content_types(cls, v):
"""Pre-normalize content types during initialization."""
if v:
return [ct.split(";")[0].strip().lower() for ct in v]
return v
# Then in matches() function, remove normalization:
if condition.content_types and context.content_type:
normalized_request = normalize_content_type(context.content_type)
# Use pre-normalized condition.content_types directly
if normalized_request not in condition.content_types:
return FalseSeverity: Medium (15-20% performance gain possible) 4. MEDIUM: Remove Unintentional CommentFile: Issue: Standalone Fix Required: Delete line 1460 Severity: Low (code cleanliness) 5. MEDIUM: Enhance Docstring ExamplesFile: Issue: Docstring doesn't demonstrate edge case handling. Fix Required: Add examples: def normalize_content_type(content_type: str) -> str:
"""Extract base content type without parameters.
Examples:
>>> normalize_content_type('application/json; charset=utf-8')
'application/json'
>>> normalize_content_type('TEXT/PLAIN')
'text/plain'
>>> normalize_content_type('') # ADD THIS
'' # ADD THIS
>>> normalize_content_type(' ') # ADD THIS
'' # ADD THIS
"""Severity: Low (documentation quality) 📊 Additional Findings SummaryPerformance Opportunities (3 items)
Security Gaps (4 items)
Code Quality (3 items)
Implementation Gaps (4 items)
Testing Gaps (6 items)
📈 Overall Assessment
🎯 Recommendation: Request ChangesThis PR successfully implements the
Additional recommendations for follow-up PRs:
Estimated effort to address required changes: 2-3 hours Legend: 🔴 Critical | |
…text-forge/pull/4005\#issuecomment-4198300827 Signed-off-by: Jitesh Nair <jiteshnair@ibm.com>
|
Hi @msureshkumar88 , |
|
Merging it now, will have this as part of PR to the main branch soon. |
03bbdc6
into
3955-bug-plugin-condition-evaluation-system-requires-comprehensive-overhaul



🐛 Bug-fix PR
Before opening this PR please:
make lint- passesruff,mypy,pylint✅make test- all unit + integration tests green ✅make coverage- ≥ 80 % ✅make docker docker-run-sslormake podman podman-run-ssl✅📌 Summary
Implements the
content_typesfield functionality forPluginConditionto enable plugins to execute conditionally based on HTTP Content-Type headers. This field was defined in the model but had no implementation, making it a non-functional configuration option that could lead to unexpected plugin behavior.Fixes: #3925
🔁 Reproduction Steps
Configure a plugin with
content_typescondition:Send requests with different Content-Type headers
Before fix: Plugin executes regardless of content type (field silently ignored)
After fix: Plugin only executes when Content-Type matches configured types
🐞 Root Cause
The
PluginCondition.content_typesfield was defined inmcpgateway/plugins/framework/models.py(line 212) but never implemented in the conditional matching logic:matches()function inutils.pyonly checkedserver_ids,tenant_ids,user_patternspayload_matches()function handledtools,prompts,resources,agentsbut notcontent_typesThis was a silent failure - the field was accepted in configuration but had no effect.
💡 Fix Description
Core Implementation
Added
content_typefield toGlobalContext(models.py):Implemented
normalize_content_type()helper (utils.py):APPLICATION/JSON→application/json)application/json; charset=utf-8→application/json)Updated
matches()function (utils.py):Extracted Content-Type at 7 instantiation sites:
auth.py(2 locations)tool_service.py(3 locations)http_auth_middleware.py(1 location)rbac.py(1 location)Testing
Created comprehensive test suite (
test_content_type_matching.py) with 21 tests covering:Documentation
docs/docs/architecture/plugins.md): Added "Content Type Filtering" section with examples, security notes, and performance benefitsdocs/docs/using/plugins/index.md): Enhanced "Conditional Execution" section with practical examples🧪 Verification
make lintmake testmake coverageTest Results:
📐 MCP Compliance (if relevant)
✅ Checklist
make black isort pre-commit)🎯 Key Behaviors
APPLICATION/JSONmatchesapplication/jsonapplication/json; charset=utf-8matchesapplication/jsoncontent_typeis None orcontent_typesnot specified, plugin executes📝 Example Configuration