Skip to content

Commit 7c7ebe7

Browse files
authored
Merge branch 'main' into fix/rust-validation-middleware-sidecar
2 parents c609a25 + 240ea19 commit 7c7ebe7

File tree

16 files changed

+1291
-49
lines changed

16 files changed

+1291
-49
lines changed

.secrets.baseline

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"files": "(^.secrets.baseline$|(^|/)Cargo\\.lock$)",
44
"lines": null
55
},
6-
"generated_at": "2026-04-08T10:09:48Z",
6+
"generated_at": "2026-04-08T09:51:23Z",
77
"plugins_used": [
88
{
99
"name": "AWSKeyDetector"
@@ -242,63 +242,63 @@
242242
"hashed_secret": "b4673e578b9b30fe8bba1b555b7b59883444c697",
243243
"is_secret": false,
244244
"is_verified": false,
245-
"line_number": 487,
245+
"line_number": 555,
246246
"type": "Secret Keyword",
247247
"verified_result": null
248248
},
249249
{
250250
"hashed_secret": "4a0a2df96d4c9a13a282268cab33ac4b8cbb2c72",
251251
"is_secret": false,
252252
"is_verified": false,
253-
"line_number": 575,
253+
"line_number": 643,
254254
"type": "Secret Keyword",
255255
"verified_result": null
256256
},
257257
{
258258
"hashed_secret": "5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8",
259259
"is_secret": false,
260260
"is_verified": false,
261-
"line_number": 925,
261+
"line_number": 993,
262262
"type": "Basic Auth Credentials",
263263
"verified_result": null
264264
},
265265
{
266266
"hashed_secret": "fa9beb99e4029ad5a6615399e7bbae21356086b3",
267267
"is_secret": false,
268268
"is_verified": false,
269-
"line_number": 2291,
269+
"line_number": 2359,
270270
"type": "Basic Auth Credentials",
271271
"verified_result": null
272272
},
273273
{
274274
"hashed_secret": "fa9beb99e4029ad5a6615399e7bbae21356086b3",
275275
"is_secret": false,
276276
"is_verified": false,
277-
"line_number": 2382,
277+
"line_number": 2450,
278278
"type": "Secret Keyword",
279279
"verified_result": null
280280
},
281281
{
282282
"hashed_secret": "ac371b6dcce28a86c90d12bc57d946a800eebf17",
283283
"is_secret": false,
284284
"is_verified": false,
285-
"line_number": 2425,
285+
"line_number": 2493,
286286
"type": "Secret Keyword",
287287
"verified_result": null
288288
},
289289
{
290290
"hashed_secret": "0b6ec68df700dec4dcd64babd0eda1edccddace1",
291291
"is_secret": false,
292292
"is_verified": false,
293-
"line_number": 2430,
293+
"line_number": 2498,
294294
"type": "Secret Keyword",
295295
"verified_result": null
296296
},
297297
{
298298
"hashed_secret": "4ad6f0082ee224001beb3ca5c3e81c8ceea5ed86",
299299
"is_secret": false,
300300
"is_verified": false,
301-
"line_number": 2435,
301+
"line_number": 2503,
302302
"type": "Secret Keyword",
303303
"verified_result": null
304304
}
@@ -1452,15 +1452,15 @@
14521452
"hashed_secret": "38297d822c960daea26f148a2fede848dcc2083b",
14531453
"is_secret": false,
14541454
"is_verified": false,
1455-
"line_number": 619,
1455+
"line_number": 711,
14561456
"type": "Secret Keyword",
14571457
"verified_result": null
14581458
},
14591459
{
14601460
"hashed_secret": "7373eb3c8f036e7f058bfc66fc27870f01231645",
14611461
"is_secret": false,
14621462
"is_verified": false,
1463-
"line_number": 1261,
1463+
"line_number": 1440,
14641464
"type": "Secret Keyword",
14651465
"verified_result": null
14661466
}

CHANGELOG.md

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,74 @@
22

33
> All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and this project **adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html)**.
44
5+
## [UNRELEASED] - BREAKING CHANGE
6+
7+
### ⚠️ Breaking Changes
8+
9+
#### **🔌 Plugin Condition Evaluation: Hybrid AND/OR Logic** ([#3930](https://github.com/IBM/mcp-context-forge/issues/3930))
10+
11+
**Action Required**: Plugin condition evaluation has changed from pure OR logic to hybrid AND/OR logic.
12+
13+
**Previous Behavior (OR Logic):**
14+
- ANY field match in ANY condition triggered plugin execution
15+
- Example: `tenant_ids: ["healthcare"], tools: ["patient_reader"]` executed if tenant=healthcare **OR** tool=patient_reader
16+
17+
**New Behavior (Hybrid AND/OR Logic):**
18+
- **Within a condition object:** ALL fields must match (AND logic)
19+
- **Across condition objects:** ANY object can match (OR logic)
20+
- Example: `tenant_ids: ["healthcare"], tools: ["patient_reader"]` executes ONLY if tenant=healthcare **AND** tool=patient_reader
21+
22+
**Impact:**
23+
- Plugins with multiple fields in a single condition object will have different execution behavior
24+
- Security policies become more precise and restrictive by default
25+
- Enables defense-in-depth with multiple required conditions
26+
27+
**Migration Steps:**
28+
29+
1. **Audit Configuration**: Run the validation script to identify affected plugins
30+
```bash
31+
python scripts/validate_plugin_conditions.py
32+
```
33+
34+
2. **Redesign Conditions**: For each flagged plugin, determine desired behavior:
35+
- **AND logic desired**: Keep fields in same object (no YAML changes needed)
36+
- **OR logic desired**: Split fields into separate condition objects
37+
38+
3. **Test Thoroughly**: Validate new behavior in non-production environment with debug logging
39+
```bash
40+
LOG_LEVEL=DEBUG python -m mcpgateway.main
41+
```
42+
43+
**Example Migration:**
44+
45+
```yaml
46+
# OLD: Executed if tenant=healthcare OR tool=patient_reader
47+
conditions:
48+
- tenant_ids: ["healthcare"]
49+
tools: ["patient_reader"]
50+
51+
# NEW Option 1: AND logic (more secure, no YAML change)
52+
conditions:
53+
- tenant_ids: ["healthcare"]
54+
tools: ["patient_reader"]
55+
# Executes ONLY if tenant=healthcare AND tool=patient_reader
56+
57+
# NEW Option 2: OR logic (split into separate objects)
58+
conditions:
59+
- tenant_ids: ["healthcare"]
60+
- tools: ["patient_reader"]
61+
# Executes if tenant=healthcare OR tool=patient_reader
62+
```
63+
64+
**Resources:**
65+
- **Migration Guide**: `docs/docs/architecture/MIGRATION-PLUGIN-CONDITIONS.md`
66+
- **Validation Script**: `scripts/validate_plugin_conditions.py`
67+
- **Architecture Docs**: `docs/docs/architecture/plugins.md#plugin-condition-evaluation`
68+
69+
**Rollback**: Keep configuration backups. Restore previous `plugins/config.yaml` if issues arise.
70+
71+
72+
573
## [1.0.0] - 2026-03-31 - General Availability
674

775
### Overview

docs/docs/architecture/plugins.md

Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
12
# Plugin Framework Specification
23

34
**Version**: 1.0
@@ -257,6 +258,97 @@ The `conditions` array contains objects that specify when plugins should execute
257258
| `user_patterns` | `string[]` | Execute for users matching regex patterns | `["admin_.*", ".*@company.com"]` |
258259
| `content_types` | `string[]` | Execute for specific content types | `["application/json", "text/plain"]` |
259260

261+
The plugin framework uses **hybrid AND/OR condition evaluation** for precise control over plugin execution.
262+
263+
#### Evaluation Logic
264+
265+
**Behavior:**
266+
- **Within a condition object**: ALL fields must match (AND logic)
267+
- **Across condition objects**: ANY object can match (OR logic)
268+
269+
This enables expressions like: `(tenant=X AND tool=Y) OR (server=Z AND user=W)`
270+
271+
**Evaluation Order within each condition object:**
272+
1. GlobalContext conditions (server_id, tenant_id, user_patterns)
273+
2. Payload-specific conditions (tools, prompts, resources, agents)
274+
275+
**Short-circuit:**
276+
- Within a condition object: Evaluation stops at the first non-matching field (fail-fast)
277+
- Across condition objects: Evaluation stops at the first fully matching object (first-match-wins)
278+
279+
#### Configuration Examples
280+
281+
#### Single Condition Object
282+
283+
```yaml
284+
plugins:
285+
- name: "TenantFilter"
286+
conditions:
287+
- tenant_ids: ["healthcare", "finance"]
288+
# Executes ONLY for healthcare OR finance tenants
289+
```
290+
291+
#### Multiple Fields in Single Condition (AND Logic)
292+
293+
```yaml
294+
plugins:
295+
- name: "PIIFilterPlugin"
296+
conditions:
297+
- tenant_ids: ["healthcare"]
298+
tools: ["patient_data_reader"]
299+
server_ids: ["prod-server"]
300+
# Executes ONLY when ALL match:
301+
# - tenant = healthcare AND
302+
# - tool = patient_data_reader AND
303+
# - server = prod-server
304+
```
305+
306+
#### Multiple Condition Objects (OR Logic)
307+
308+
```yaml
309+
plugins:
310+
- name: "SecurityPlugin"
311+
conditions:
312+
- tenant_ids: ["enterprise"]
313+
tools: ["sensitive_tool"]
314+
- server_ids: ["prod-server"]
315+
user_patterns: ["admin_.*"]
316+
# Executes when:
317+
# (tenant=enterprise AND tool=sensitive_tool) OR
318+
# (server=prod-server AND user matches admin_.*)
319+
#
320+
# This means the plugin runs if EITHER:
321+
# - Request is from enterprise tenant using sensitive_tool, OR
322+
# - Request is on prod-server from a user matching admin_.*
323+
```
324+
325+
### Use Cases
326+
327+
| Scenario | Configuration Pattern |
328+
|----------|----------------------|
329+
| Tenant-specific plugin | `tenant_ids: ["tenant1"]` |
330+
| Tool-specific security | `tools: ["sensitive_tool"]` |
331+
| Multi-factor control | `tenant_ids: [...], tools: [...], user_patterns: [...]` (all in one object) |
332+
| Production-only | `server_ids: ["prod-server"]` |
333+
| Admin-only operations | `user_patterns: ["admin_.*"]` |
334+
| Multiple independent scenarios | Multiple condition objects with different field combinations |
335+
336+
#### Debugging Condition Evaluation
337+
338+
Enable debug logging to see condition evaluation details:
339+
340+
```bash
341+
LOG_LEVEL=DEBUG python -m mcpgateway.main
342+
```
343+
344+
Log output includes:
345+
- Number of condition objects being evaluated
346+
- Which condition object is being checked (1/N, 2/N, etc.)
347+
- GlobalContext mismatch details (server_id, tenant_id, user)
348+
- Payload mismatch details (tool name, prompt_id, etc.)
349+
- Success message when condition fully matches
350+
- Final execution decision (execute or skip)
351+
260352
#### MCP Configuration Fields
261353

262354
For external plugins (`kind: "external"`), the `mcp` object configures the MCP server connection:
@@ -1010,6 +1102,93 @@ class PluginCondition(BaseModel):
10101102
content_types: Optional[list[str]] = None # Execute for specific content types
10111103
```
10121104

1105+
#### Content Type Filtering
1106+
1107+
Plugins can be configured to execute only for specific content types using the `content_types` condition. This enables fine-grained control over when plugins process requests based on the HTTP `Content-Type` header.
1108+
1109+
**Configuration Example:**
1110+
1111+
```yaml
1112+
plugins:
1113+
- name: "JsonValidator"
1114+
kind: "plugins.json_validator.JsonValidator"
1115+
hooks: ["tool_pre_invoke"]
1116+
conditions:
1117+
- content_types: ["application/json"]
1118+
```
1119+
1120+
**Matching Behavior:**
1121+
1122+
- **Case-insensitive**: `APPLICATION/JSON` matches `application/json`
1123+
- **Parameter stripping**: `application/json; charset=utf-8` matches `application/json`
1124+
- **Multiple types**: Supports OR logic - plugin executes if any content type matches
1125+
- **Permissive default**: If `content_types` is not specified or request has no Content-Type header, plugin executes normally
1126+
1127+
**Common Content Types:**
1128+
1129+
| Content Type | Description | Use Case |
1130+
|--------------|-------------|----------|
1131+
| `application/json` | JSON data | API requests, structured data validation |
1132+
| `text/plain` | Plain text | Simple text processing, logging |
1133+
| `text/html` | HTML documents | Web scraping, content extraction |
1134+
| `application/xml` | XML data | Legacy API integration, SOAP services |
1135+
| `multipart/form-data` | File uploads | File validation, virus scanning |
1136+
| `application/x-www-form-urlencoded` | Form submissions | Form data validation |
1137+
1138+
**Example: JSON-Only Security Plugin**
1139+
1140+
```yaml
1141+
plugins:
1142+
- name: "JsonSecurityScanner"
1143+
kind: "plugins.security.json_scanner.JsonSecurityScanner"
1144+
hooks: ["tool_pre_invoke", "tool_post_invoke"]
1145+
mode: "enforce"
1146+
priority: 10
1147+
conditions:
1148+
- content_types: ["application/json"]
1149+
server_ids: ["production-api"]
1150+
```
1151+
1152+
**Example: Multi-Format Data Validator**
1153+
1154+
```yaml
1155+
plugins:
1156+
- name: "DataValidator"
1157+
kind: "plugins.validation.data_validator.DataValidator"
1158+
hooks: ["tool_pre_invoke"]
1159+
conditions:
1160+
- content_types:
1161+
- "application/json"
1162+
- "application/xml"
1163+
- "text/csv"
1164+
```
1165+
1166+
**Combined Conditions:**
1167+
1168+
Content type filtering works seamlessly with other conditions:
1169+
1170+
```yaml
1171+
plugins:
1172+
- name: "TeamJsonProcessor"
1173+
kind: "plugins.processors.json_processor.JsonProcessor"
1174+
hooks: ["tool_pre_invoke"]
1175+
conditions:
1176+
- content_types: ["application/json"]
1177+
tenant_ids: ["team-alpha", "team-beta"]
1178+
tools: ["data_analysis", "report_generation"]
1179+
```
1180+
1181+
**Security Considerations:**
1182+
1183+
!!! warning "Content-Type Spoofing"
1184+
Clients can set arbitrary Content-Type headers. Use `content_types` for filtering and optimization, not as a security boundary. Combine with other conditions (server_ids, tenant_ids) for defense-in-depth.
1185+
1186+
**Performance Benefits:**
1187+
1188+
- **Reduced overhead**: Skip expensive processing for irrelevant content types
1189+
- **Targeted validation**: Apply format-specific validators only when needed
1190+
- **Resource optimization**: Prevent unnecessary plugin execution
1191+
10131192
## Hook Reference Documentation
10141193

10151194
The plugin framework provides two main categories of hooks, each documented in detail in separate files:

0 commit comments

Comments
 (0)