Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,11 @@ mcpgateway/test_migration.db
*.log
logs/
log/
reports/
coverage/
artifacts/
.tmp/
tmp/

# Certificates and secrets
certs/
Expand Down Expand Up @@ -297,6 +302,7 @@ docs/build/

# PyBuilder
target/
**/target/

# Jupyter Notebook
.ipynb_checkpoints
Expand Down
23 changes: 12 additions & 11 deletions plugins/pii_filter/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -120,31 +120,31 @@ config:
### Run All Tests
```bash
# Run all PII filter tests
pytest tests/unit/mcpgateway/plugins/pii_filter/test_pii_filter.py -v
pytest tests/unit/mcpgateway/plugins/plugins/pii_filter/test_pii_filter.py -v

# Run with coverage
pytest tests/unit/mcpgateway/plugins/pii_filter/test_pii_filter.py --cov=plugins.pii_filter --cov-report=term-missing
pytest tests/unit/mcpgateway/plugins/plugins/pii_filter/test_pii_filter.py --cov=plugins.pii_filter --cov-report=term-missing
```

### Run Specific Test Classes
```bash
# Test only the detector functionality
pytest tests/unit/mcpgateway/plugins/pii_filter/test_pii_filter.py::TestPIIDetector -v
pytest tests/unit/mcpgateway/plugins/plugins/pii_filter/test_pii_filter.py::TestPIIDetector -v

# Test only the plugin integration
pytest tests/unit/mcpgateway/plugins/pii_filter/test_pii_filter.py::TestPIIFilterPlugin -v
pytest tests/unit/mcpgateway/plugins/plugins/pii_filter/test_pii_filter.py::TestPIIFilterPlugin -v
```

### Run Individual Tests
```bash
# Test SSN detection
pytest tests/unit/mcpgateway/plugins/pii_filter/test_pii_filter.py::TestPIIDetector::test_ssn_detection -v
pytest tests/unit/mcpgateway/plugins/plugins/pii_filter/test_pii_filter.py::TestPIIDetector::test_ssn_detection -v

# Test masking strategies
pytest tests/unit/mcpgateway/plugins/pii_filter/test_pii_filter.py::TestPIIDetector::test_masking_strategies -v
pytest tests/unit/mcpgateway/plugins/plugins/pii_filter/test_pii_filter.py::TestPIIDetector::test_masking_strategies -v

# Test blocking mode
pytest tests/unit/mcpgateway/plugins/pii_filter/test_pii_filter.py::TestPIIFilterPlugin::test_prompt_pre_fetch_blocking -v
pytest tests/unit/mcpgateway/plugins/plugins/pii_filter/test_pii_filter.py::TestPIIFilterPlugin::test_prompt_pre_fetch_blocking -v
```

### Manual Testing with the Gateway
Expand All @@ -154,18 +154,19 @@ pytest tests/unit/mcpgateway/plugins/pii_filter/test_pii_filter.py::TestPIIFilte
PLUGINS_ENABLED=true
```

2. Start the gateway:
2. Start the gateway.
Direct app port defaults to `http://localhost:4444`; the compose stack is typically exposed through nginx at `http://localhost:8080`.
```bash
python -m mcpgateway.main
```

3. Test with curl:
```bash
# Test PII detection in prompt arguments
curl -X POST http://localhost:8000/prompts/test_prompt \
curl -X POST http://localhost:4444/prompts/test_prompt \
-H "Content-Type: application/json" \
-d '{
"args": {
"arguments": {
"user_input": "My SSN is 123-45-6789 and email is john@example.com"
}
}'
Expand Down Expand Up @@ -312,7 +313,7 @@ DOB: 01/15/1985
export MCPGATEWAY_BEARER_TOKEN=$(python3 -m mcpgateway.utils.create_jwt_token -u admin@example.com --secret my-test-key)

# Then test with a prompt containing various PII
curl -X GET "http://localhost:4444/prompts/test_prompt" \
curl -X POST "http://localhost:4444/prompts/test_prompt" \
-H "Authorization: Bearer $MCPGATEWAY_BEARER_TOKEN" \
-H "Content-Type: application/json" \
-d '{
Expand Down
10 changes: 10 additions & 0 deletions plugins/pii_filter/pii_filter.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,11 @@
_RUST_AVAILABLE = False


_PYTHON_PLUGIN_DEPRECATION_MESSAGE = (
"The legacy Python PII filter detector is deprecated and will be removed in a future release. Install the Rust-backed `pii_filter_rust` package to keep the PII filter plugin enabled."
)


class PIIType(str, Enum):
"""Types of PII that can be detected."""

Expand Down Expand Up @@ -452,6 +457,8 @@ def _apply_mask(self, value: str, pii_type: PIIType, strategy: MaskingStrategy)
class PIIFilterPlugin(Plugin):
"""PII Filter plugin for detecting and masking sensitive information."""

_python_deprecation_warned = False

def __init__(self, config: PluginConfig):
"""Initialize the PII filter plugin.

Expand All @@ -469,6 +476,9 @@ def __init__(self, config: PluginConfig):
else:
self.detector = PIIDetector(self.pii_config)
self.implementation = "Python"
if not self.__class__._python_deprecation_warned:
logger.warning(_PYTHON_PLUGIN_DEPRECATION_MESSAGE)
self.__class__._python_deprecation_warned = True
logger.info("🐍 PIIFilterPlugin initialized with Python implementation")

self.detection_count = 0
Expand Down
2 changes: 1 addition & 1 deletion plugins_rust/pii_filter/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions plugins_rust/pii_filter/python/pii_filter_rust/__init__.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

import builtins
import typing

__all__ = [
"PIIDetectorRust",
]
Expand Down Expand Up @@ -64,10 +65,10 @@ class PIIDetectorRust:
```python
{
"ssn": [
{"value": "123-45-6789", "start": 10, "end": 21, "mask_strategy": "partial"}
{"value": "123-45-6789", "start": 10, "end": 21, "mask_strategy": "redact"}
],
"email": [
{"value": "john@example.com", "start": 35, "end": 51, "mask_strategy": "partial"}
{"value": "john@example.com", "start": 35, "end": 51, "mask_strategy": "redact"}
]
}
```
Expand Down
65 changes: 60 additions & 5 deletions plugins_rust/pii_filter/src/detector.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ use super::patterns::{CompiledPatterns, compile_patterns};
pub fn detect_pii(
text: &str,
patterns: &CompiledPatterns,
_config: &PIIConfig,
config: &PIIConfig,
) -> HashMap<PIIType, Vec<Detection>> {
let mut detections: HashMap<PIIType, Vec<Detection>> = HashMap::new();

Expand All @@ -33,7 +33,9 @@ pub fn detect_pii(
value: mat.as_str().to_string(),
start: mat.start(),
end: mat.end(),
mask_strategy: pattern.mask_strategy,
mask_strategy: pattern
.mask_strategy
.unwrap_or(config.default_mask_strategy),
};

detections
Expand Down Expand Up @@ -132,10 +134,10 @@ impl PIIDetectorRust {
/// ```python
/// {
/// "ssn": [
/// {"value": "123-45-6789", "start": 10, "end": 21, "mask_strategy": "partial"}
/// {"value": "123-45-6789", "start": 10, "end": 21, "mask_strategy": "redact"}
/// ],
/// "email": [
/// {"value": "john@example.com", "start": 35, "end": 51, "mask_strategy": "partial"}
/// {"value": "john@example.com", "start": 35, "end": 51, "mask_strategy": "redact"}
/// ]
/// }
/// ```
Expand Down Expand Up @@ -347,7 +349,9 @@ impl PIIDetectorRust {
value,
start,
end,
mask_strategy: pattern.mask_strategy,
mask_strategy: pattern
.mask_strategy
.unwrap_or(self.config.default_mask_strategy),
};

detections
Expand Down Expand Up @@ -544,4 +548,55 @@ mod tests {
let total: usize = detections.values().map(|v| v.len()).sum();
assert!(total >= 1);
}

#[test]
fn test_default_mask_strategy_applies_to_built_in_patterns() {
let config = PIIConfig {
detect_ssn: true,
detect_email: true,
detect_phone: false,
detect_ip_address: false,
default_mask_strategy: MaskingStrategy::Redact,
redaction_text: "[PII_REDACTED]".to_string(),
..Default::default()
};
let patterns = compile_patterns(&config).unwrap();
let detector = PIIDetectorRust { patterns, config };

let detections = detector.detect_internal("SSN: 123-45-6789 Email: john@example.com");

assert_eq!(
detections[&PIIType::Ssn][0].mask_strategy,
MaskingStrategy::Redact
);
assert_eq!(
detections[&PIIType::Email][0].mask_strategy,
MaskingStrategy::Redact
);
}

#[test]
fn test_custom_patterns_keep_explicit_mask_strategy() {
let mut config = PIIConfig {
default_mask_strategy: MaskingStrategy::Redact,
..Default::default()
};
config
.custom_patterns
.push(super::super::config::CustomPattern {
pattern: r"\bEMP\d{6}\b".to_string(),
description: "Employee ID".to_string(),
mask_strategy: MaskingStrategy::Partial,
enabled: true,
});

let patterns = compile_patterns(&config).unwrap();
let detector = PIIDetectorRust { patterns, config };
let detections = detector.detect_internal("Employee ID EMP123456");

assert_eq!(
detections[&PIIType::Custom][0].mask_strategy,
MaskingStrategy::Partial
);
}
}
Loading
Loading