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.
- main.py +112 -0
- mcpower_proxy-0.0.58.dist-info/METADATA +250 -0
- mcpower_proxy-0.0.58.dist-info/RECORD +43 -0
- mcpower_proxy-0.0.58.dist-info/WHEEL +5 -0
- mcpower_proxy-0.0.58.dist-info/entry_points.txt +2 -0
- mcpower_proxy-0.0.58.dist-info/licenses/LICENSE +201 -0
- mcpower_proxy-0.0.58.dist-info/top_level.txt +3 -0
- modules/__init__.py +1 -0
- modules/apis/__init__.py +1 -0
- modules/apis/security_policy.py +322 -0
- modules/logs/__init__.py +1 -0
- modules/logs/audit_trail.py +162 -0
- modules/logs/logger.py +128 -0
- modules/redaction/__init__.py +13 -0
- modules/redaction/constants.py +38 -0
- modules/redaction/gitleaks_rules.py +1268 -0
- modules/redaction/pii_rules.py +271 -0
- modules/redaction/redactor.py +599 -0
- modules/ui/__init__.py +1 -0
- modules/ui/classes.py +48 -0
- modules/ui/confirmation.py +200 -0
- modules/ui/simple_dialog.py +104 -0
- modules/ui/xdialog/__init__.py +249 -0
- modules/ui/xdialog/constants.py +13 -0
- modules/ui/xdialog/mac_dialogs.py +190 -0
- modules/ui/xdialog/tk_dialogs.py +78 -0
- modules/ui/xdialog/windows_custom_dialog.py +426 -0
- modules/ui/xdialog/windows_dialogs.py +250 -0
- modules/ui/xdialog/windows_structs.py +183 -0
- modules/ui/xdialog/yad_dialogs.py +236 -0
- modules/ui/xdialog/zenity_dialogs.py +156 -0
- modules/utils/__init__.py +1 -0
- modules/utils/cli.py +46 -0
- modules/utils/config.py +193 -0
- modules/utils/copy.py +36 -0
- modules/utils/ids.py +160 -0
- modules/utils/json.py +120 -0
- modules/utils/mcp_configs.py +48 -0
- wrapper/__init__.py +1 -0
- wrapper/__version__.py +6 -0
- wrapper/middleware.py +750 -0
- wrapper/schema.py +227 -0
- 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)
|