tweek 0.1.0__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.
- tweek/__init__.py +16 -0
- tweek/cli.py +3390 -0
- tweek/cli_helpers.py +193 -0
- tweek/config/__init__.py +13 -0
- tweek/config/allowed_dirs.yaml +23 -0
- tweek/config/manager.py +1064 -0
- tweek/config/patterns.yaml +751 -0
- tweek/config/tiers.yaml +129 -0
- tweek/diagnostics.py +589 -0
- tweek/hooks/__init__.py +1 -0
- tweek/hooks/pre_tool_use.py +861 -0
- tweek/integrations/__init__.py +3 -0
- tweek/integrations/moltbot.py +243 -0
- tweek/licensing.py +398 -0
- tweek/logging/__init__.py +9 -0
- tweek/logging/bundle.py +350 -0
- tweek/logging/json_logger.py +150 -0
- tweek/logging/security_log.py +745 -0
- tweek/mcp/__init__.py +24 -0
- tweek/mcp/approval.py +456 -0
- tweek/mcp/approval_cli.py +356 -0
- tweek/mcp/clients/__init__.py +37 -0
- tweek/mcp/clients/chatgpt.py +112 -0
- tweek/mcp/clients/claude_desktop.py +203 -0
- tweek/mcp/clients/gemini.py +178 -0
- tweek/mcp/proxy.py +667 -0
- tweek/mcp/screening.py +175 -0
- tweek/mcp/server.py +317 -0
- tweek/platform/__init__.py +131 -0
- tweek/plugins/__init__.py +835 -0
- tweek/plugins/base.py +1080 -0
- tweek/plugins/compliance/__init__.py +30 -0
- tweek/plugins/compliance/gdpr.py +333 -0
- tweek/plugins/compliance/gov.py +324 -0
- tweek/plugins/compliance/hipaa.py +285 -0
- tweek/plugins/compliance/legal.py +322 -0
- tweek/plugins/compliance/pci.py +361 -0
- tweek/plugins/compliance/soc2.py +275 -0
- tweek/plugins/detectors/__init__.py +30 -0
- tweek/plugins/detectors/continue_dev.py +206 -0
- tweek/plugins/detectors/copilot.py +254 -0
- tweek/plugins/detectors/cursor.py +192 -0
- tweek/plugins/detectors/moltbot.py +205 -0
- tweek/plugins/detectors/windsurf.py +214 -0
- tweek/plugins/git_discovery.py +395 -0
- tweek/plugins/git_installer.py +491 -0
- tweek/plugins/git_lockfile.py +338 -0
- tweek/plugins/git_registry.py +503 -0
- tweek/plugins/git_security.py +482 -0
- tweek/plugins/providers/__init__.py +30 -0
- tweek/plugins/providers/anthropic.py +181 -0
- tweek/plugins/providers/azure_openai.py +289 -0
- tweek/plugins/providers/bedrock.py +248 -0
- tweek/plugins/providers/google.py +197 -0
- tweek/plugins/providers/openai.py +230 -0
- tweek/plugins/scope.py +130 -0
- tweek/plugins/screening/__init__.py +26 -0
- tweek/plugins/screening/llm_reviewer.py +149 -0
- tweek/plugins/screening/pattern_matcher.py +273 -0
- tweek/plugins/screening/rate_limiter.py +174 -0
- tweek/plugins/screening/session_analyzer.py +159 -0
- tweek/proxy/__init__.py +302 -0
- tweek/proxy/addon.py +223 -0
- tweek/proxy/interceptor.py +313 -0
- tweek/proxy/server.py +315 -0
- tweek/sandbox/__init__.py +71 -0
- tweek/sandbox/executor.py +382 -0
- tweek/sandbox/linux.py +278 -0
- tweek/sandbox/profile_generator.py +323 -0
- tweek/screening/__init__.py +13 -0
- tweek/screening/context.py +81 -0
- tweek/security/__init__.py +22 -0
- tweek/security/llm_reviewer.py +348 -0
- tweek/security/rate_limiter.py +682 -0
- tweek/security/secret_scanner.py +506 -0
- tweek/security/session_analyzer.py +600 -0
- tweek/vault/__init__.py +40 -0
- tweek/vault/cross_platform.py +251 -0
- tweek/vault/keychain.py +288 -0
- tweek-0.1.0.dist-info/METADATA +335 -0
- tweek-0.1.0.dist-info/RECORD +85 -0
- tweek-0.1.0.dist-info/WHEEL +5 -0
- tweek-0.1.0.dist-info/entry_points.txt +25 -0
- tweek-0.1.0.dist-info/licenses/LICENSE +190 -0
- tweek-0.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Tweek HIPAA Compliance Plugin
|
|
4
|
+
|
|
5
|
+
Detects Protected Health Information (PHI) identifiers:
|
|
6
|
+
- Patient identifiers (MRN, patient ID)
|
|
7
|
+
- Medical record references
|
|
8
|
+
- Diagnosis codes (ICD-10)
|
|
9
|
+
- Prescription information
|
|
10
|
+
- Healthcare facility identifiers
|
|
11
|
+
- Insurance information
|
|
12
|
+
|
|
13
|
+
Based on the 18 HIPAA identifiers that constitute PHI.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from typing import Optional, List, Dict, Any
|
|
17
|
+
from tweek.plugins.base import (
|
|
18
|
+
CompliancePlugin,
|
|
19
|
+
ScanDirection,
|
|
20
|
+
ActionType,
|
|
21
|
+
Severity,
|
|
22
|
+
PatternDefinition,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class HIPAACompliancePlugin(CompliancePlugin):
|
|
27
|
+
"""
|
|
28
|
+
HIPAA/PHI compliance plugin.
|
|
29
|
+
|
|
30
|
+
Detects patterns that may indicate Protected Health Information (PHI)
|
|
31
|
+
as defined by HIPAA regulations.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
VERSION = "1.0.0"
|
|
35
|
+
DESCRIPTION = "Detect HIPAA Protected Health Information (PHI) patterns"
|
|
36
|
+
AUTHOR = "Tweek"
|
|
37
|
+
REQUIRES_LICENSE = "enterprise"
|
|
38
|
+
TAGS = ["compliance", "hipaa", "healthcare", "phi"]
|
|
39
|
+
|
|
40
|
+
def __init__(self, config: Optional[Dict[str, Any]] = None):
|
|
41
|
+
super().__init__(config)
|
|
42
|
+
self._patterns: Optional[List[PatternDefinition]] = None
|
|
43
|
+
|
|
44
|
+
@property
|
|
45
|
+
def name(self) -> str:
|
|
46
|
+
return "hipaa"
|
|
47
|
+
|
|
48
|
+
@property
|
|
49
|
+
def scan_direction(self) -> ScanDirection:
|
|
50
|
+
direction = self._config.get("scan_direction", "both")
|
|
51
|
+
return ScanDirection(direction)
|
|
52
|
+
|
|
53
|
+
def get_patterns(self) -> List[PatternDefinition]:
|
|
54
|
+
"""Return HIPAA PHI patterns."""
|
|
55
|
+
if self._patterns is not None:
|
|
56
|
+
return self._patterns
|
|
57
|
+
|
|
58
|
+
self._patterns = [
|
|
59
|
+
# =================================================================
|
|
60
|
+
# Patient Identifiers
|
|
61
|
+
# =================================================================
|
|
62
|
+
PatternDefinition(
|
|
63
|
+
name="mrn",
|
|
64
|
+
regex=r"(?i)(?:MRN|MEDICAL\s*RECORD\s*(?:NUMBER|#|NO\.?))[:\s#]*[A-Z]?\d{5,12}",
|
|
65
|
+
severity=Severity.HIGH,
|
|
66
|
+
description="Medical Record Number",
|
|
67
|
+
default_action=ActionType.WARN,
|
|
68
|
+
tags=["phi", "identifier", "mrn"],
|
|
69
|
+
),
|
|
70
|
+
PatternDefinition(
|
|
71
|
+
name="patient_id",
|
|
72
|
+
regex=r"(?i)PATIENT\s*(?:ID|#|NUMBER|NO\.?)[:\s#]*[\w-]{4,20}",
|
|
73
|
+
severity=Severity.HIGH,
|
|
74
|
+
description="Patient identifier",
|
|
75
|
+
default_action=ActionType.WARN,
|
|
76
|
+
tags=["phi", "identifier"],
|
|
77
|
+
),
|
|
78
|
+
PatternDefinition(
|
|
79
|
+
name="account_number",
|
|
80
|
+
regex=r"(?i)(?:HOSPITAL|MEDICAL|PATIENT)\s*(?:ACCOUNT|ACCT)\s*(?:#|NUMBER|NO\.?)[:\s#]*\d{6,15}",
|
|
81
|
+
severity=Severity.HIGH,
|
|
82
|
+
description="Medical account number",
|
|
83
|
+
default_action=ActionType.WARN,
|
|
84
|
+
tags=["phi", "identifier", "account"],
|
|
85
|
+
),
|
|
86
|
+
|
|
87
|
+
# =================================================================
|
|
88
|
+
# Diagnosis Codes
|
|
89
|
+
# =================================================================
|
|
90
|
+
PatternDefinition(
|
|
91
|
+
name="icd10_code",
|
|
92
|
+
regex=r"\b[A-TV-Z]\d{2}(?:\.\d{1,4})?\b",
|
|
93
|
+
severity=Severity.MEDIUM,
|
|
94
|
+
description="ICD-10 diagnosis code",
|
|
95
|
+
default_action=ActionType.WARN,
|
|
96
|
+
tags=["phi", "diagnosis", "icd10"],
|
|
97
|
+
),
|
|
98
|
+
PatternDefinition(
|
|
99
|
+
name="diagnosis_context",
|
|
100
|
+
regex=r"(?i)(?:DIAGNOSED?\s+WITH|DX|DIAGNOSIS)[:\s]+[\w\s]{3,50}",
|
|
101
|
+
severity=Severity.MEDIUM,
|
|
102
|
+
description="Diagnosis context",
|
|
103
|
+
default_action=ActionType.WARN,
|
|
104
|
+
tags=["phi", "diagnosis"],
|
|
105
|
+
),
|
|
106
|
+
PatternDefinition(
|
|
107
|
+
name="cpt_code",
|
|
108
|
+
regex=r"(?i)CPT[:\s#]*\d{5}",
|
|
109
|
+
severity=Severity.MEDIUM,
|
|
110
|
+
description="CPT procedure code",
|
|
111
|
+
default_action=ActionType.WARN,
|
|
112
|
+
tags=["phi", "procedure", "cpt"],
|
|
113
|
+
),
|
|
114
|
+
|
|
115
|
+
# =================================================================
|
|
116
|
+
# Prescription Information
|
|
117
|
+
# =================================================================
|
|
118
|
+
PatternDefinition(
|
|
119
|
+
name="prescription",
|
|
120
|
+
regex=r"(?i)(?:PRESCRIBED?|RX|MEDICATION)[:\s]+[\w\s-]+(?:\d+\s*(?:mg|ml|mcg|g|units?))",
|
|
121
|
+
severity=Severity.MEDIUM,
|
|
122
|
+
description="Prescription or medication with dosage",
|
|
123
|
+
default_action=ActionType.WARN,
|
|
124
|
+
tags=["phi", "prescription"],
|
|
125
|
+
),
|
|
126
|
+
PatternDefinition(
|
|
127
|
+
name="dea_number",
|
|
128
|
+
regex=r"(?i)DEA\s*(?:#|NUMBER|NO\.?)?[:\s]*[A-Z]{2}\d{7}",
|
|
129
|
+
severity=Severity.HIGH,
|
|
130
|
+
description="DEA number (prescriber identifier)",
|
|
131
|
+
default_action=ActionType.WARN,
|
|
132
|
+
tags=["phi", "identifier", "dea"],
|
|
133
|
+
),
|
|
134
|
+
PatternDefinition(
|
|
135
|
+
name="ndc_code",
|
|
136
|
+
regex=r"(?i)NDC[:\s#]*\d{4,5}-\d{3,4}-\d{1,2}",
|
|
137
|
+
severity=Severity.MEDIUM,
|
|
138
|
+
description="National Drug Code",
|
|
139
|
+
default_action=ActionType.WARN,
|
|
140
|
+
tags=["phi", "medication", "ndc"],
|
|
141
|
+
),
|
|
142
|
+
|
|
143
|
+
# =================================================================
|
|
144
|
+
# Insurance Information
|
|
145
|
+
# =================================================================
|
|
146
|
+
PatternDefinition(
|
|
147
|
+
name="health_plan_id",
|
|
148
|
+
regex=r"(?i)(?:HEALTH\s*PLAN|INSURANCE|POLICY)\s*(?:ID|#|NUMBER|NO\.?)[:\s#]*[\w-]{6,20}",
|
|
149
|
+
severity=Severity.HIGH,
|
|
150
|
+
description="Health plan/insurance identifier",
|
|
151
|
+
default_action=ActionType.WARN,
|
|
152
|
+
tags=["phi", "insurance"],
|
|
153
|
+
),
|
|
154
|
+
PatternDefinition(
|
|
155
|
+
name="medicare_id",
|
|
156
|
+
regex=r"(?i)MEDICARE\s*(?:ID|#|NUMBER|NO\.?|BENEFICIARY)[:\s#]*\d[A-Z]\d[A-Z]-?[A-Z]{2}\d-?[A-Z]{2}\d{2}",
|
|
157
|
+
severity=Severity.HIGH,
|
|
158
|
+
description="Medicare Beneficiary Identifier",
|
|
159
|
+
default_action=ActionType.WARN,
|
|
160
|
+
tags=["phi", "medicare", "identifier"],
|
|
161
|
+
),
|
|
162
|
+
PatternDefinition(
|
|
163
|
+
name="medicaid_id",
|
|
164
|
+
regex=r"(?i)MEDICAID\s*(?:ID|#|NUMBER|NO\.?)[:\s#]*[\w-]{8,15}",
|
|
165
|
+
severity=Severity.HIGH,
|
|
166
|
+
description="Medicaid identifier",
|
|
167
|
+
default_action=ActionType.WARN,
|
|
168
|
+
tags=["phi", "medicaid", "identifier"],
|
|
169
|
+
),
|
|
170
|
+
|
|
171
|
+
# =================================================================
|
|
172
|
+
# Provider Identifiers
|
|
173
|
+
# =================================================================
|
|
174
|
+
PatternDefinition(
|
|
175
|
+
name="npi",
|
|
176
|
+
regex=r"(?i)NPI[:\s#]*\d{10}",
|
|
177
|
+
severity=Severity.MEDIUM,
|
|
178
|
+
description="National Provider Identifier",
|
|
179
|
+
default_action=ActionType.WARN,
|
|
180
|
+
tags=["phi", "provider", "npi"],
|
|
181
|
+
),
|
|
182
|
+
PatternDefinition(
|
|
183
|
+
name="physician_context",
|
|
184
|
+
regex=r"(?i)(?:ATTENDING|TREATING|PRIMARY)\s*(?:PHYSICIAN|DOCTOR|DR\.?)[:\s]+[\w\s]{3,40}",
|
|
185
|
+
severity=Severity.LOW,
|
|
186
|
+
description="Physician reference with context",
|
|
187
|
+
default_action=ActionType.WARN,
|
|
188
|
+
tags=["phi", "provider"],
|
|
189
|
+
),
|
|
190
|
+
|
|
191
|
+
# =================================================================
|
|
192
|
+
# Facility Information
|
|
193
|
+
# =================================================================
|
|
194
|
+
PatternDefinition(
|
|
195
|
+
name="facility_id",
|
|
196
|
+
regex=r"(?i)(?:FACILITY|HOSPITAL|CLINIC)\s*(?:ID|#|NUMBER|NO\.?)[:\s#]*[\w-]{4,20}",
|
|
197
|
+
severity=Severity.MEDIUM,
|
|
198
|
+
description="Healthcare facility identifier",
|
|
199
|
+
default_action=ActionType.WARN,
|
|
200
|
+
tags=["phi", "facility"],
|
|
201
|
+
),
|
|
202
|
+
PatternDefinition(
|
|
203
|
+
name="bed_location",
|
|
204
|
+
regex=r"(?i)(?:ROOM|BED|UNIT)[:\s#]*[\w-]{1,10}",
|
|
205
|
+
severity=Severity.LOW,
|
|
206
|
+
description="Patient room/bed location",
|
|
207
|
+
default_action=ActionType.WARN,
|
|
208
|
+
tags=["phi", "location"],
|
|
209
|
+
),
|
|
210
|
+
|
|
211
|
+
# =================================================================
|
|
212
|
+
# Medical Conditions (Sensitive)
|
|
213
|
+
# =================================================================
|
|
214
|
+
PatternDefinition(
|
|
215
|
+
name="hiv_status",
|
|
216
|
+
regex=r"(?i)HIV\s*(?:\+|\-|POSITIVE|NEGATIVE|STATUS|TEST)",
|
|
217
|
+
severity=Severity.CRITICAL,
|
|
218
|
+
description="HIV status information",
|
|
219
|
+
default_action=ActionType.BLOCK,
|
|
220
|
+
tags=["phi", "sensitive", "hiv"],
|
|
221
|
+
),
|
|
222
|
+
PatternDefinition(
|
|
223
|
+
name="mental_health",
|
|
224
|
+
regex=r"(?i)(?:PSYCH(?:IATRIC)?|MENTAL\s*HEALTH)\s*(?:DIAGNOSIS|HISTORY|TREATMENT|EVAL)",
|
|
225
|
+
severity=Severity.HIGH,
|
|
226
|
+
description="Mental health information",
|
|
227
|
+
default_action=ActionType.WARN,
|
|
228
|
+
tags=["phi", "sensitive", "mental-health"],
|
|
229
|
+
),
|
|
230
|
+
PatternDefinition(
|
|
231
|
+
name="substance_abuse",
|
|
232
|
+
regex=r"(?i)(?:SUBSTANCE\s*ABUSE|DRUG\s*(?:ABUSE|ADDICTION)|ALCOHOL(?:ISM)?)\s*(?:HISTORY|TREATMENT|PROGRAM)",
|
|
233
|
+
severity=Severity.HIGH,
|
|
234
|
+
description="Substance abuse information",
|
|
235
|
+
default_action=ActionType.WARN,
|
|
236
|
+
tags=["phi", "sensitive", "substance-abuse"],
|
|
237
|
+
),
|
|
238
|
+
|
|
239
|
+
# =================================================================
|
|
240
|
+
# Dates (PHI when combined with health info)
|
|
241
|
+
# =================================================================
|
|
242
|
+
PatternDefinition(
|
|
243
|
+
name="date_of_service",
|
|
244
|
+
regex=r"(?i)(?:DATE\s*OF\s*(?:SERVICE|VISIT|ADMISSION|DISCHARGE)|DOS)[:\s]*\d{1,2}[-/]\d{1,2}[-/]\d{2,4}",
|
|
245
|
+
severity=Severity.LOW,
|
|
246
|
+
description="Date of healthcare service",
|
|
247
|
+
default_action=ActionType.WARN,
|
|
248
|
+
tags=["phi", "date"],
|
|
249
|
+
),
|
|
250
|
+
PatternDefinition(
|
|
251
|
+
name="dob_context",
|
|
252
|
+
regex=r"(?i)(?:DOB|DATE\s*OF\s*BIRTH|BIRTH\s*DATE)[:\s]*\d{1,2}[-/]\d{1,2}[-/]\d{2,4}",
|
|
253
|
+
severity=Severity.HIGH,
|
|
254
|
+
description="Date of birth in healthcare context",
|
|
255
|
+
default_action=ActionType.WARN,
|
|
256
|
+
tags=["phi", "date", "dob"],
|
|
257
|
+
),
|
|
258
|
+
]
|
|
259
|
+
|
|
260
|
+
return self._patterns
|
|
261
|
+
|
|
262
|
+
def _format_message(
|
|
263
|
+
self,
|
|
264
|
+
findings: List,
|
|
265
|
+
direction: ScanDirection
|
|
266
|
+
) -> Optional[str]:
|
|
267
|
+
"""Format a HIPAA-specific message."""
|
|
268
|
+
if not findings:
|
|
269
|
+
return None
|
|
270
|
+
|
|
271
|
+
# Categorize findings
|
|
272
|
+
high_severity = [f for f in findings if f.severity in (Severity.HIGH, Severity.CRITICAL)]
|
|
273
|
+
|
|
274
|
+
if direction == ScanDirection.OUTPUT:
|
|
275
|
+
msg = f"WARNING: LLM output may contain {len(findings)} PHI indicator(s).\n"
|
|
276
|
+
if high_severity:
|
|
277
|
+
msg += f" {len(high_severity)} HIGH/CRITICAL severity pattern(s) detected.\n"
|
|
278
|
+
msg += "Review output before sharing to ensure HIPAA compliance."
|
|
279
|
+
return msg
|
|
280
|
+
else:
|
|
281
|
+
msg = f"ALERT: Input may contain {len(findings)} PHI indicator(s).\n"
|
|
282
|
+
if high_severity:
|
|
283
|
+
msg += f" {len(high_severity)} HIGH/CRITICAL severity pattern(s) detected.\n"
|
|
284
|
+
msg += "Ensure this data is handled in accordance with HIPAA requirements."
|
|
285
|
+
return msg
|
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Tweek Legal Compliance Plugin
|
|
4
|
+
|
|
5
|
+
Detects legal privilege and confidentiality markers:
|
|
6
|
+
- Attorney-client privilege
|
|
7
|
+
- Work product doctrine
|
|
8
|
+
- Confidential communications
|
|
9
|
+
- Settlement discussions
|
|
10
|
+
- Trade secrets
|
|
11
|
+
- NDA-protected information
|
|
12
|
+
|
|
13
|
+
Helps prevent inadvertent waiver of legal privileges through LLM processing.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from typing import Optional, List, Dict, Any
|
|
17
|
+
from tweek.plugins.base import (
|
|
18
|
+
CompliancePlugin,
|
|
19
|
+
ScanDirection,
|
|
20
|
+
ActionType,
|
|
21
|
+
Severity,
|
|
22
|
+
PatternDefinition,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class LegalCompliancePlugin(CompliancePlugin):
|
|
27
|
+
"""
|
|
28
|
+
Legal privilege and confidentiality compliance plugin.
|
|
29
|
+
|
|
30
|
+
Detects markers indicating legally protected communications
|
|
31
|
+
that should not be processed by external AI systems.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
VERSION = "1.0.0"
|
|
35
|
+
DESCRIPTION = "Detect legal privilege and confidentiality markers"
|
|
36
|
+
AUTHOR = "Tweek"
|
|
37
|
+
REQUIRES_LICENSE = "enterprise"
|
|
38
|
+
TAGS = ["compliance", "legal", "privilege", "confidential"]
|
|
39
|
+
|
|
40
|
+
def __init__(self, config: Optional[Dict[str, Any]] = None):
|
|
41
|
+
super().__init__(config)
|
|
42
|
+
self._patterns: Optional[List[PatternDefinition]] = None
|
|
43
|
+
|
|
44
|
+
@property
|
|
45
|
+
def name(self) -> str:
|
|
46
|
+
return "legal"
|
|
47
|
+
|
|
48
|
+
@property
|
|
49
|
+
def scan_direction(self) -> ScanDirection:
|
|
50
|
+
direction = self._config.get("scan_direction", "both")
|
|
51
|
+
return ScanDirection(direction)
|
|
52
|
+
|
|
53
|
+
def get_patterns(self) -> List[PatternDefinition]:
|
|
54
|
+
"""Return legal privilege patterns."""
|
|
55
|
+
if self._patterns is not None:
|
|
56
|
+
return self._patterns
|
|
57
|
+
|
|
58
|
+
self._patterns = [
|
|
59
|
+
# =================================================================
|
|
60
|
+
# Attorney-Client Privilege
|
|
61
|
+
# =================================================================
|
|
62
|
+
PatternDefinition(
|
|
63
|
+
name="attorney_client_privilege",
|
|
64
|
+
regex=r"(?i)ATTORNEY[\s-]*CLIENT\s+PRIVILEG(?:E|ED)",
|
|
65
|
+
severity=Severity.HIGH,
|
|
66
|
+
description="Attorney-client privilege marker",
|
|
67
|
+
default_action=ActionType.WARN,
|
|
68
|
+
tags=["privilege", "attorney-client"],
|
|
69
|
+
),
|
|
70
|
+
PatternDefinition(
|
|
71
|
+
name="privileged_confidential",
|
|
72
|
+
regex=r"(?i)PRIVILEGED\s+(?:AND\s+)?CONFIDENTIAL",
|
|
73
|
+
severity=Severity.HIGH,
|
|
74
|
+
description="Privileged and confidential marker",
|
|
75
|
+
default_action=ActionType.WARN,
|
|
76
|
+
tags=["privilege", "confidential"],
|
|
77
|
+
),
|
|
78
|
+
PatternDefinition(
|
|
79
|
+
name="legal_privilege",
|
|
80
|
+
regex=r"(?i)(?:SUBJECT\s+TO|PROTECTED\s+BY)\s+(?:LEGAL\s+)?PRIVILEG(?:E|ED)",
|
|
81
|
+
severity=Severity.HIGH,
|
|
82
|
+
description="Legal privilege protection marker",
|
|
83
|
+
default_action=ActionType.WARN,
|
|
84
|
+
tags=["privilege"],
|
|
85
|
+
),
|
|
86
|
+
PatternDefinition(
|
|
87
|
+
name="solicitor_privilege",
|
|
88
|
+
regex=r"(?i)SOLICITOR[\s-]*CLIENT\s+PRIVILEG(?:E|ED)|LEGAL\s+PROFESSIONAL\s+PRIVILEGE",
|
|
89
|
+
severity=Severity.HIGH,
|
|
90
|
+
description="Solicitor-client privilege (UK/Commonwealth)",
|
|
91
|
+
default_action=ActionType.WARN,
|
|
92
|
+
tags=["privilege", "solicitor-client"],
|
|
93
|
+
),
|
|
94
|
+
|
|
95
|
+
# =================================================================
|
|
96
|
+
# Work Product Doctrine
|
|
97
|
+
# =================================================================
|
|
98
|
+
PatternDefinition(
|
|
99
|
+
name="work_product",
|
|
100
|
+
regex=r"(?i)(?:ATTORNEY|LEGAL|LAWYER)\s+WORK\s+PRODUCT",
|
|
101
|
+
severity=Severity.HIGH,
|
|
102
|
+
description="Attorney work product doctrine",
|
|
103
|
+
default_action=ActionType.WARN,
|
|
104
|
+
tags=["privilege", "work-product"],
|
|
105
|
+
),
|
|
106
|
+
PatternDefinition(
|
|
107
|
+
name="trial_preparation",
|
|
108
|
+
regex=r"(?i)TRIAL\s+PREPARATION\s+MATERIALS?",
|
|
109
|
+
severity=Severity.HIGH,
|
|
110
|
+
description="Trial preparation materials",
|
|
111
|
+
default_action=ActionType.WARN,
|
|
112
|
+
tags=["privilege", "work-product"],
|
|
113
|
+
),
|
|
114
|
+
PatternDefinition(
|
|
115
|
+
name="litigation_hold",
|
|
116
|
+
regex=r"(?i)LITIGATION\s+HOLD|LEGAL\s+HOLD|PRESERVATION\s+NOTICE",
|
|
117
|
+
severity=Severity.MEDIUM,
|
|
118
|
+
description="Litigation hold notice",
|
|
119
|
+
default_action=ActionType.WARN,
|
|
120
|
+
tags=["litigation"],
|
|
121
|
+
),
|
|
122
|
+
|
|
123
|
+
# =================================================================
|
|
124
|
+
# Confidential Communications
|
|
125
|
+
# =================================================================
|
|
126
|
+
PatternDefinition(
|
|
127
|
+
name="confidential_header",
|
|
128
|
+
regex=r"(?i)^[-=]*\s*CONFIDENTIAL\s*[-=]*$",
|
|
129
|
+
severity=Severity.MEDIUM,
|
|
130
|
+
description="Confidential header/footer",
|
|
131
|
+
default_action=ActionType.WARN,
|
|
132
|
+
tags=["confidential"],
|
|
133
|
+
),
|
|
134
|
+
PatternDefinition(
|
|
135
|
+
name="strictly_confidential",
|
|
136
|
+
regex=r"(?i)STRICTLY\s+CONFIDENTIAL",
|
|
137
|
+
severity=Severity.HIGH,
|
|
138
|
+
description="Strictly confidential marker",
|
|
139
|
+
default_action=ActionType.WARN,
|
|
140
|
+
tags=["confidential"],
|
|
141
|
+
),
|
|
142
|
+
PatternDefinition(
|
|
143
|
+
name="confidential_info",
|
|
144
|
+
regex=r"(?i)THIS\s+(?:DOCUMENT|COMMUNICATION|EMAIL|MESSAGE)\s+(?:IS|CONTAINS?)\s+CONFIDENTIAL",
|
|
145
|
+
severity=Severity.MEDIUM,
|
|
146
|
+
description="Confidential communication notice",
|
|
147
|
+
default_action=ActionType.WARN,
|
|
148
|
+
tags=["confidential"],
|
|
149
|
+
),
|
|
150
|
+
|
|
151
|
+
# =================================================================
|
|
152
|
+
# Settlement and Mediation
|
|
153
|
+
# =================================================================
|
|
154
|
+
PatternDefinition(
|
|
155
|
+
name="settlement_privilege",
|
|
156
|
+
regex=r"(?i)SETTLEMENT\s+(?:PRIVILEGE|NEGOTIATIONS?|DISCUSSION)",
|
|
157
|
+
severity=Severity.HIGH,
|
|
158
|
+
description="Settlement privilege/negotiations",
|
|
159
|
+
default_action=ActionType.WARN,
|
|
160
|
+
tags=["privilege", "settlement"],
|
|
161
|
+
),
|
|
162
|
+
PatternDefinition(
|
|
163
|
+
name="mediation_confidential",
|
|
164
|
+
regex=r"(?i)(?:MEDIATION|ARBITRATION)\s+(?:PRIVILEGE|CONFIDENTIAL)",
|
|
165
|
+
severity=Severity.HIGH,
|
|
166
|
+
description="Mediation/arbitration confidentiality",
|
|
167
|
+
default_action=ActionType.WARN,
|
|
168
|
+
tags=["privilege", "adr"],
|
|
169
|
+
),
|
|
170
|
+
PatternDefinition(
|
|
171
|
+
name="without_prejudice",
|
|
172
|
+
regex=r"(?i)WITHOUT\s+PREJUDICE",
|
|
173
|
+
severity=Severity.MEDIUM,
|
|
174
|
+
description="Without prejudice marker (settlement protection)",
|
|
175
|
+
default_action=ActionType.WARN,
|
|
176
|
+
tags=["privilege", "settlement"],
|
|
177
|
+
),
|
|
178
|
+
PatternDefinition(
|
|
179
|
+
name="rule_408",
|
|
180
|
+
regex=r"(?i)(?:FRE|FEDERAL\s+RULES?\s+OF\s+EVIDENCE)\s+(?:RULE\s+)?408|SETTLEMENT\s+COMMUNICATIONS?",
|
|
181
|
+
severity=Severity.MEDIUM,
|
|
182
|
+
description="FRE 408 settlement communication",
|
|
183
|
+
default_action=ActionType.WARN,
|
|
184
|
+
tags=["privilege", "settlement"],
|
|
185
|
+
),
|
|
186
|
+
|
|
187
|
+
# =================================================================
|
|
188
|
+
# Trade Secrets
|
|
189
|
+
# =================================================================
|
|
190
|
+
PatternDefinition(
|
|
191
|
+
name="trade_secret",
|
|
192
|
+
regex=r"(?i)TRADE\s+SECRET|PROPRIETARY\s+(?:AND\s+)?CONFIDENTIAL",
|
|
193
|
+
severity=Severity.HIGH,
|
|
194
|
+
description="Trade secret marker",
|
|
195
|
+
default_action=ActionType.WARN,
|
|
196
|
+
tags=["trade-secret", "confidential"],
|
|
197
|
+
),
|
|
198
|
+
PatternDefinition(
|
|
199
|
+
name="dtsa_protected",
|
|
200
|
+
regex=r"(?i)(?:DTSA|DEFEND\s+TRADE\s+SECRETS\s+ACT)\s+PROTECTED",
|
|
201
|
+
severity=Severity.HIGH,
|
|
202
|
+
description="DTSA protected information",
|
|
203
|
+
default_action=ActionType.WARN,
|
|
204
|
+
tags=["trade-secret"],
|
|
205
|
+
),
|
|
206
|
+
|
|
207
|
+
# =================================================================
|
|
208
|
+
# Non-Disclosure Agreements
|
|
209
|
+
# =================================================================
|
|
210
|
+
PatternDefinition(
|
|
211
|
+
name="nda_protected",
|
|
212
|
+
regex=r"(?i)(?:NDA|NON[\s-]?DISCLOSURE\s+AGREEMENT)\s+PROTECTED",
|
|
213
|
+
severity=Severity.MEDIUM,
|
|
214
|
+
description="NDA-protected information",
|
|
215
|
+
default_action=ActionType.WARN,
|
|
216
|
+
tags=["nda", "confidential"],
|
|
217
|
+
),
|
|
218
|
+
PatternDefinition(
|
|
219
|
+
name="confidentiality_agreement",
|
|
220
|
+
regex=r"(?i)(?:SUBJECT\s+TO|PROTECTED\s+BY|COVERED\s+BY)\s+(?:A\s+)?(?:CONFIDENTIALITY|NON[\s-]?DISCLOSURE)\s+AGREEMENT",
|
|
221
|
+
severity=Severity.MEDIUM,
|
|
222
|
+
description="Confidentiality agreement reference",
|
|
223
|
+
default_action=ActionType.WARN,
|
|
224
|
+
tags=["nda", "confidential"],
|
|
225
|
+
),
|
|
226
|
+
|
|
227
|
+
# =================================================================
|
|
228
|
+
# Regulatory and Compliance
|
|
229
|
+
# =================================================================
|
|
230
|
+
PatternDefinition(
|
|
231
|
+
name="export_controlled",
|
|
232
|
+
regex=r"(?i)(?:EXPORT\s+CONTROLLED?|ITAR|EAR)\s+(?:INFORMATION|DATA|MATERIAL)",
|
|
233
|
+
severity=Severity.HIGH,
|
|
234
|
+
description="Export controlled information (ITAR/EAR)",
|
|
235
|
+
default_action=ActionType.WARN,
|
|
236
|
+
tags=["export-control", "regulatory"],
|
|
237
|
+
),
|
|
238
|
+
PatternDefinition(
|
|
239
|
+
name="material_non_public",
|
|
240
|
+
regex=r"(?i)MATERIAL\s+NON[\s-]?PUBLIC\s+INFORMATION|MNPI",
|
|
241
|
+
severity=Severity.CRITICAL,
|
|
242
|
+
description="Material non-public information (insider trading)",
|
|
243
|
+
default_action=ActionType.BLOCK,
|
|
244
|
+
tags=["mnpi", "securities"],
|
|
245
|
+
),
|
|
246
|
+
|
|
247
|
+
# =================================================================
|
|
248
|
+
# Legal Disclaimers and Warnings
|
|
249
|
+
# =================================================================
|
|
250
|
+
PatternDefinition(
|
|
251
|
+
name="unauthorized_disclosure",
|
|
252
|
+
regex=r"(?i)UNAUTHORIZED\s+(?:DISCLOSURE|DISTRIBUTION|USE|ACCESS)\s+(?:IS\s+)?(?:STRICTLY\s+)?PROHIBITED",
|
|
253
|
+
severity=Severity.MEDIUM,
|
|
254
|
+
description="Unauthorized disclosure warning",
|
|
255
|
+
default_action=ActionType.WARN,
|
|
256
|
+
tags=["disclaimer"],
|
|
257
|
+
),
|
|
258
|
+
PatternDefinition(
|
|
259
|
+
name="intended_recipient",
|
|
260
|
+
regex=r"(?i)(?:INTENDED\s+(?:ONLY\s+)?FOR|SOLELY\s+FOR)\s+(?:THE\s+)?(?:NAMED\s+)?RECIPIENT",
|
|
261
|
+
severity=Severity.LOW,
|
|
262
|
+
description="Intended recipient notice",
|
|
263
|
+
default_action=ActionType.WARN,
|
|
264
|
+
tags=["disclaimer"],
|
|
265
|
+
),
|
|
266
|
+
PatternDefinition(
|
|
267
|
+
name="delete_if_received",
|
|
268
|
+
regex=r"(?i)(?:IF\s+YOU\s+(?:HAVE\s+)?RECEIVED\s+THIS\s+(?:IN\s+)?ERROR|PLEASE\s+DELETE|NOTIFY\s+THE\s+SENDER)",
|
|
269
|
+
severity=Severity.LOW,
|
|
270
|
+
description="Error receipt notice",
|
|
271
|
+
default_action=ActionType.WARN,
|
|
272
|
+
tags=["disclaimer"],
|
|
273
|
+
),
|
|
274
|
+
|
|
275
|
+
# =================================================================
|
|
276
|
+
# Legal Document Types
|
|
277
|
+
# =================================================================
|
|
278
|
+
PatternDefinition(
|
|
279
|
+
name="draft_document",
|
|
280
|
+
regex=r"(?i)(?:DRAFT|PRELIMINARY)\s+[-–]\s+(?:PRIVILEGED|CONFIDENTIAL|NOT\s+FOR\s+DISTRIBUTION)",
|
|
281
|
+
severity=Severity.MEDIUM,
|
|
282
|
+
description="Draft/preliminary document marker",
|
|
283
|
+
default_action=ActionType.WARN,
|
|
284
|
+
tags=["draft", "confidential"],
|
|
285
|
+
),
|
|
286
|
+
PatternDefinition(
|
|
287
|
+
name="legal_memo",
|
|
288
|
+
regex=r"(?i)(?:ATTORNEY|LEGAL)\s+(?:MEMORANDUM|MEMO)\s+[-–]\s+(?:PRIVILEGED|CONFIDENTIAL)",
|
|
289
|
+
severity=Severity.HIGH,
|
|
290
|
+
description="Legal memorandum marker",
|
|
291
|
+
default_action=ActionType.WARN,
|
|
292
|
+
tags=["memo", "privilege"],
|
|
293
|
+
),
|
|
294
|
+
]
|
|
295
|
+
|
|
296
|
+
return self._patterns
|
|
297
|
+
|
|
298
|
+
def _format_message(
|
|
299
|
+
self,
|
|
300
|
+
findings: List,
|
|
301
|
+
direction: ScanDirection
|
|
302
|
+
) -> Optional[str]:
|
|
303
|
+
"""Format a legal-specific message."""
|
|
304
|
+
if not findings:
|
|
305
|
+
return None
|
|
306
|
+
|
|
307
|
+
privilege_findings = [f for f in findings if "privilege" in f.metadata.get("pattern_tags", [])]
|
|
308
|
+
trade_secret_findings = [f for f in findings if "trade-secret" in f.metadata.get("pattern_tags", [])]
|
|
309
|
+
|
|
310
|
+
if direction == ScanDirection.OUTPUT:
|
|
311
|
+
msg = f"WARNING: LLM output contains {len(findings)} legal/privilege marker(s).\n"
|
|
312
|
+
msg += "These may be hallucinated and do not confer actual privilege.\n"
|
|
313
|
+
msg += "Do NOT rely on these markers for legal protection."
|
|
314
|
+
else:
|
|
315
|
+
msg = f"ALERT: Input contains {len(findings)} legal/privilege marker(s).\n"
|
|
316
|
+
if privilege_findings:
|
|
317
|
+
msg += f" {len(privilege_findings)} privilege marker(s) - may constitute waiver if shared\n"
|
|
318
|
+
if trade_secret_findings:
|
|
319
|
+
msg += f" {len(trade_secret_findings)} trade secret marker(s)\n"
|
|
320
|
+
msg += "Processing privileged content through external AI may waive protections."
|
|
321
|
+
|
|
322
|
+
return msg
|