mcpower-proxy 0.0.58__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. main.py +112 -0
  2. mcpower_proxy-0.0.58.dist-info/METADATA +250 -0
  3. mcpower_proxy-0.0.58.dist-info/RECORD +43 -0
  4. mcpower_proxy-0.0.58.dist-info/WHEEL +5 -0
  5. mcpower_proxy-0.0.58.dist-info/entry_points.txt +2 -0
  6. mcpower_proxy-0.0.58.dist-info/licenses/LICENSE +201 -0
  7. mcpower_proxy-0.0.58.dist-info/top_level.txt +3 -0
  8. modules/__init__.py +1 -0
  9. modules/apis/__init__.py +1 -0
  10. modules/apis/security_policy.py +322 -0
  11. modules/logs/__init__.py +1 -0
  12. modules/logs/audit_trail.py +162 -0
  13. modules/logs/logger.py +128 -0
  14. modules/redaction/__init__.py +13 -0
  15. modules/redaction/constants.py +38 -0
  16. modules/redaction/gitleaks_rules.py +1268 -0
  17. modules/redaction/pii_rules.py +271 -0
  18. modules/redaction/redactor.py +599 -0
  19. modules/ui/__init__.py +1 -0
  20. modules/ui/classes.py +48 -0
  21. modules/ui/confirmation.py +200 -0
  22. modules/ui/simple_dialog.py +104 -0
  23. modules/ui/xdialog/__init__.py +249 -0
  24. modules/ui/xdialog/constants.py +13 -0
  25. modules/ui/xdialog/mac_dialogs.py +190 -0
  26. modules/ui/xdialog/tk_dialogs.py +78 -0
  27. modules/ui/xdialog/windows_custom_dialog.py +426 -0
  28. modules/ui/xdialog/windows_dialogs.py +250 -0
  29. modules/ui/xdialog/windows_structs.py +183 -0
  30. modules/ui/xdialog/yad_dialogs.py +236 -0
  31. modules/ui/xdialog/zenity_dialogs.py +156 -0
  32. modules/utils/__init__.py +1 -0
  33. modules/utils/cli.py +46 -0
  34. modules/utils/config.py +193 -0
  35. modules/utils/copy.py +36 -0
  36. modules/utils/ids.py +160 -0
  37. modules/utils/json.py +120 -0
  38. modules/utils/mcp_configs.py +48 -0
  39. wrapper/__init__.py +1 -0
  40. wrapper/__version__.py +6 -0
  41. wrapper/middleware.py +750 -0
  42. wrapper/schema.py +227 -0
  43. wrapper/server.py +78 -0
@@ -0,0 +1,271 @@
1
+ """
2
+ Lightweight PII detection using only regex patterns.
3
+ No external dependencies beyond Python's built-in re module.
4
+ """
5
+
6
+ import re
7
+ from typing import List, NamedTuple
8
+
9
+
10
+ class PIIMatch(NamedTuple):
11
+ """Represents a detected PII match."""
12
+ start: int
13
+ end: int
14
+ entity_type: str
15
+ confidence: float
16
+
17
+
18
+ class URLDetector:
19
+ """URL detector with protocol requirement and intelligent boundary detection."""
20
+
21
+ def __init__(self):
22
+ # Common protocols that use :// format
23
+ protocols = r'(?:https?|ftps?|sftp|ssh|wss?|git|file|telnet|ldaps?|smb|nfs)'
24
+ self.pattern = re.compile(
25
+ rf'{protocols}://[^\s]+',
26
+ re.IGNORECASE
27
+ )
28
+ self.sentence_enders = '.,:;!?\'"'
29
+
30
+ def extract(self, text: str) -> List[PIIMatch]:
31
+ """Extract URLs with proper boundary detection."""
32
+ matches = []
33
+ for match in self.pattern.finditer(text):
34
+ cleaned_url = self._clean_url(match.group())
35
+ if cleaned_url:
36
+ end = match.start() + len(cleaned_url)
37
+ matches.append(PIIMatch(
38
+ start=match.start(),
39
+ end=end,
40
+ entity_type='URL',
41
+ confidence=0.85
42
+ ))
43
+ return matches
44
+
45
+ def _clean_url(self, url: str) -> str:
46
+ """Remove trailing punctuation intelligently."""
47
+ url = url.rstrip(self.sentence_enders)
48
+
49
+ # Balance paired delimiters
50
+ for opener, closer in [('(', ')'), ('[', ']'), ('{', '}')]:
51
+ while url.endswith(closer):
52
+ if url.count(opener) >= url.count(closer):
53
+ break
54
+ url = url[:-1]
55
+
56
+ return url
57
+
58
+
59
+ class PIIDetector:
60
+ """Lightweight PII detector using only regex patterns."""
61
+
62
+ def __init__(self):
63
+ # URL detector with intelligent boundary detection
64
+ self.url_detector = URLDetector()
65
+
66
+ # Compile regex patterns for better performance
67
+ self.patterns = {
68
+ 'EMAIL_ADDRESS': re.compile(
69
+ r'\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b',
70
+ re.IGNORECASE
71
+ ),
72
+ 'CREDIT_CARD': re.compile(
73
+ r'\b(?:'
74
+ r'4[0-9]{3}[-\s]?[0-9]{4}[-\s]?[0-9]{4}[-\s]?[0-9]{4}(?:[0-9]{3})?|' # Visa with formatting
75
+ r'5[1-5][0-9]{2}[-\s]?[0-9]{4}[-\s]?[0-9]{4}[-\s]?[0-9]{4}|' # MasterCard with formatting
76
+ r'3[47][0-9]{2}[-\s]?[0-9]{6}[-\s]?[0-9]{5}|' # Amex with formatting
77
+ r'4[0-9]{12}(?:[0-9]{3})?|' # Visa without formatting
78
+ r'5[1-5][0-9]{14}|' # MasterCard without formatting
79
+ r'3[47][0-9]{13}|' # American Express without formatting
80
+ r'3[0-9]{13}|' # Diners Club
81
+ r'6(?:011|5[0-9]{2})[0-9]{12}' # Discover
82
+ r')\b'
83
+ ),
84
+ 'IP_ADDRESS': re.compile(
85
+ r'(?:'
86
+ # IPv4
87
+ r'\b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}'
88
+ r'(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\b'
89
+ r'|'
90
+ # IPv6 - comprehensive pattern
91
+ r'(?:'
92
+ # Full IPv6 or with :: compression
93
+ r'(?:[0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|' # Full: 1:2:3:4:5:6:7:8
94
+ r'(?:[0-9a-fA-F]{1,4}:){1,7}:|' # Compressed trailing: 1:: or 1:2:3:4:5:6:7::
95
+ r'(?:[0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|' # Compressed middle: 1::8 or 1:2:3:4:5:6::8
96
+ r'(?:[0-9a-fA-F]{1,4}:){1,5}(?::[0-9a-fA-F]{1,4}){1,2}|' # 1::7:8 or 1:2:3:4:5::7:8
97
+ r'(?:[0-9a-fA-F]{1,4}:){1,4}(?::[0-9a-fA-F]{1,4}){1,3}|' # 1::6:7:8 or 1:2:3:4::6:7:8
98
+ r'(?:[0-9a-fA-F]{1,4}:){1,3}(?::[0-9a-fA-F]{1,4}){1,4}|' # 1::5:6:7:8 or 1:2:3::5:6:7:8
99
+ r'(?:[0-9a-fA-F]{1,4}:){1,2}(?::[0-9a-fA-F]{1,4}){1,5}|' # 1::4:5:6:7:8 or 1:2::4:5:6:7:8
100
+ r'[0-9a-fA-F]{1,4}:(?:(?::[0-9a-fA-F]{1,4}){1,6})|' # 1::3:4:5:6:7:8
101
+ r':(?:(?::[0-9a-fA-F]{1,4}){1,7}|:)|' # ::2:3:4:5:6:7:8 or ::
102
+ # IPv4-mapped IPv6: ::ffff:192.0.2.1
103
+ r'(?:[0-9a-fA-F]{1,4}:){1,4}:'
104
+ r'(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}'
105
+ r'(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)'
106
+ r')'
107
+ r')',
108
+ re.IGNORECASE
109
+ ),
110
+ # Common crypto addresses
111
+ 'CRYPTO_ADDRESS': re.compile(
112
+ r'\b(?:'
113
+ r'[13][a-km-zA-HJ-NP-Z1-9]{25,34}|' # Bitcoin
114
+ r'0x[a-fA-F0-9]{40}|' # Ethereum
115
+ r'[LM3][a-km-zA-HJ-NP-Z1-9]{26,33}' # Litecoin
116
+ r')\b'
117
+ ),
118
+ # IBAN (International Bank Account Number)
119
+ 'IBAN': re.compile(
120
+ r'\b[A-Z]{2}[0-9]{2}[A-Z0-9]{4}[0-9]{7}([A-Z0-9]?){0,16}\b'
121
+ ),
122
+ }
123
+
124
+ def validate_credit_card(self, number: str) -> bool:
125
+ """Validate credit card using Luhn algorithm"""
126
+ digits = re.sub(r'\D', '', number) # Remove non-digits
127
+ if not digits:
128
+ return False
129
+
130
+ total = 0
131
+ for i, digit in enumerate(reversed(digits)):
132
+ n = int(digit)
133
+ if i % 2 == 1:
134
+ n *= 2
135
+ if n > 9:
136
+ n -= 9
137
+ total += n
138
+ return total % 10 == 0
139
+
140
+ def validate_iban(self, iban: str) -> bool:
141
+ """Validate IBAN using MOD-97 algorithm"""
142
+ # Remove spaces and convert to uppercase
143
+ iban = re.sub(r'\s', '', iban).upper()
144
+
145
+ # IBAN must be at least 15 characters
146
+ if len(iban) < 15:
147
+ return False
148
+
149
+ # Move first 4 characters to the end
150
+ rearranged_iban = iban[4:] + iban[:4]
151
+
152
+ # Convert letters to numbers (A=10, B=11, ..., Z=35)
153
+ numeric_iban = ""
154
+ for char in rearranged_iban:
155
+ if char.isdigit():
156
+ numeric_iban += char
157
+ elif char.isalpha():
158
+ numeric_iban += str(ord(char) - ord('A') + 10)
159
+ else:
160
+ return False # Invalid character
161
+
162
+ try:
163
+ return int(numeric_iban) % 97 == 1
164
+ except ValueError:
165
+ return False
166
+
167
+ def analyze(self, text: str) -> List[PIIMatch]:
168
+ """
169
+ Analyze text and return detected PII matches.
170
+
171
+ Args:
172
+ text: Input text to analyze
173
+
174
+ Returns:
175
+ List of PIIMatch objects with detected PII
176
+ """
177
+ matches = []
178
+
179
+ # Extract URLs using URLDetector
180
+ matches.extend(self.url_detector.extract(text))
181
+
182
+ # Extract other PII using regex patterns
183
+ for entity_type, pattern in self.patterns.items():
184
+ for match in pattern.finditer(text):
185
+ matched_text = match.group()
186
+
187
+ # Calculate base confidence
188
+ confidence = self._calculate_confidence(entity_type, matched_text)
189
+
190
+ # Validation gates - boost confidence for validated entities
191
+ if entity_type == 'CREDIT_CARD':
192
+ if not self.validate_credit_card(matched_text):
193
+ continue # Skip if Luhn validation fails
194
+ confidence = 0.99 # Near-certainty for validated credit cards
195
+
196
+ if entity_type == 'IBAN':
197
+ if not self.validate_iban(matched_text):
198
+ continue # Skip if MOD-97 validation fails
199
+ confidence = 0.99 # Near-certainty for validated IBANs
200
+
201
+ matches.append(PIIMatch(
202
+ start=match.start(),
203
+ end=match.end(),
204
+ entity_type=entity_type,
205
+ confidence=confidence
206
+ ))
207
+
208
+ # Sort by start position and remove overlaps (keep highest confidence)
209
+ return self._resolve_overlaps(matches)
210
+
211
+ def _calculate_confidence(self, entity_type: str, matched_text: str) -> float:
212
+ """Calculate confidence score based on entity type and matched text."""
213
+ # Base confidence scores
214
+ base_scores = {
215
+ 'EMAIL_ADDRESS': 0.95,
216
+ 'CREDIT_CARD': 0.85, # Will be 0.99 after Luhn validation
217
+ 'IP_ADDRESS': 0.90,
218
+ 'URL': 0.80,
219
+ 'CRYPTO_ADDRESS': 0.95,
220
+ 'IBAN': 0.85, # Will be 0.99 after MOD-97 validation
221
+ }
222
+
223
+ return base_scores.get(entity_type, 0.5)
224
+
225
+ def _resolve_overlaps(self, matches: List[PIIMatch]) -> List[PIIMatch]:
226
+ """Resolve overlapping matches by keeping the highest confidence one."""
227
+ if not matches:
228
+ return []
229
+
230
+ # Sort by start position, then by confidence (descending)
231
+ sorted_matches = sorted(matches, key=lambda m: (m.start, -m.confidence))
232
+ resolved = []
233
+
234
+ for current in sorted_matches:
235
+ # Check if current match overlaps with any already resolved match
236
+ overlaps = False
237
+ for existing in resolved:
238
+ if not (current.end <= existing.start or current.start >= existing.end):
239
+ # There's an overlap - keep the higher confidence one
240
+ if current.confidence > existing.confidence:
241
+ resolved.remove(existing)
242
+ resolved.append(current)
243
+ overlaps = True
244
+ break
245
+
246
+ if not overlaps:
247
+ resolved.append(current)
248
+
249
+ # Sort final results by start position
250
+ return sorted(resolved, key=lambda m: m.start)
251
+
252
+
253
+ # Global instance for easy access
254
+ _detector = None
255
+
256
+
257
+ def detect_pii(text: str) -> List[PIIMatch]:
258
+ """
259
+ Detect PII in text using regex patterns.
260
+
261
+ Args:
262
+ text: Input text to analyze
263
+
264
+ Returns:
265
+ List of PIIMatch objects with detected PII
266
+ """
267
+ global _detector
268
+ if _detector is None:
269
+ _detector = PIIDetector()
270
+
271
+ return _detector.analyze(text)