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,361 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Tweek PCI-DSS Compliance Plugin
|
|
4
|
+
|
|
5
|
+
Detects payment card industry data:
|
|
6
|
+
- Credit/debit card numbers (with Luhn validation)
|
|
7
|
+
- CVV/CVC codes
|
|
8
|
+
- Bank account and routing numbers
|
|
9
|
+
- Payment tokens and references
|
|
10
|
+
- Cardholder data markers
|
|
11
|
+
|
|
12
|
+
Based on PCI-DSS requirements for protecting cardholder data.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from typing import Optional, List, Dict, Any
|
|
16
|
+
from tweek.plugins.base import (
|
|
17
|
+
CompliancePlugin,
|
|
18
|
+
ScanDirection,
|
|
19
|
+
ActionType,
|
|
20
|
+
Severity,
|
|
21
|
+
PatternDefinition,
|
|
22
|
+
Finding,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def luhn_checksum(card_number: str) -> bool:
|
|
27
|
+
"""
|
|
28
|
+
Validate a card number using the Luhn algorithm.
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
card_number: Card number string (digits only)
|
|
32
|
+
|
|
33
|
+
Returns:
|
|
34
|
+
True if valid according to Luhn algorithm
|
|
35
|
+
"""
|
|
36
|
+
# Remove non-digits
|
|
37
|
+
digits = ''.join(c for c in card_number if c.isdigit())
|
|
38
|
+
|
|
39
|
+
if len(digits) < 13 or len(digits) > 19:
|
|
40
|
+
return False
|
|
41
|
+
|
|
42
|
+
# Luhn algorithm
|
|
43
|
+
total = 0
|
|
44
|
+
reverse_digits = digits[::-1]
|
|
45
|
+
|
|
46
|
+
for i, digit in enumerate(reverse_digits):
|
|
47
|
+
n = int(digit)
|
|
48
|
+
if i % 2 == 1:
|
|
49
|
+
n *= 2
|
|
50
|
+
if n > 9:
|
|
51
|
+
n -= 9
|
|
52
|
+
total += n
|
|
53
|
+
|
|
54
|
+
return total % 10 == 0
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class PCICompliancePlugin(CompliancePlugin):
|
|
58
|
+
"""
|
|
59
|
+
PCI-DSS compliance plugin.
|
|
60
|
+
|
|
61
|
+
Detects payment card data and related financial information
|
|
62
|
+
that must be protected under PCI-DSS requirements.
|
|
63
|
+
"""
|
|
64
|
+
|
|
65
|
+
VERSION = "1.0.0"
|
|
66
|
+
DESCRIPTION = "Detect PCI-DSS payment card and financial data"
|
|
67
|
+
AUTHOR = "Tweek"
|
|
68
|
+
REQUIRES_LICENSE = "enterprise"
|
|
69
|
+
TAGS = ["compliance", "pci", "financial", "payment"]
|
|
70
|
+
|
|
71
|
+
def __init__(self, config: Optional[Dict[str, Any]] = None):
|
|
72
|
+
super().__init__(config)
|
|
73
|
+
self._patterns: Optional[List[PatternDefinition]] = None
|
|
74
|
+
self._validate_luhn = config.get("validate_luhn", True) if config else True
|
|
75
|
+
|
|
76
|
+
@property
|
|
77
|
+
def name(self) -> str:
|
|
78
|
+
return "pci"
|
|
79
|
+
|
|
80
|
+
@property
|
|
81
|
+
def scan_direction(self) -> ScanDirection:
|
|
82
|
+
direction = self._config.get("scan_direction", "both")
|
|
83
|
+
return ScanDirection(direction)
|
|
84
|
+
|
|
85
|
+
def get_patterns(self) -> List[PatternDefinition]:
|
|
86
|
+
"""Return PCI-DSS patterns."""
|
|
87
|
+
if self._patterns is not None:
|
|
88
|
+
return self._patterns
|
|
89
|
+
|
|
90
|
+
self._patterns = [
|
|
91
|
+
# =================================================================
|
|
92
|
+
# Credit Card Numbers
|
|
93
|
+
# =================================================================
|
|
94
|
+
PatternDefinition(
|
|
95
|
+
name="visa",
|
|
96
|
+
regex=r"\b4[0-9]{3}[\s-]?[0-9]{4}[\s-]?[0-9]{4}[\s-]?[0-9]{4}\b",
|
|
97
|
+
severity=Severity.CRITICAL,
|
|
98
|
+
description="Visa card number",
|
|
99
|
+
default_action=ActionType.REDACT,
|
|
100
|
+
tags=["pci", "card", "visa"],
|
|
101
|
+
),
|
|
102
|
+
PatternDefinition(
|
|
103
|
+
name="mastercard",
|
|
104
|
+
regex=r"\b5[1-5][0-9]{2}[\s-]?[0-9]{4}[\s-]?[0-9]{4}[\s-]?[0-9]{4}\b",
|
|
105
|
+
severity=Severity.CRITICAL,
|
|
106
|
+
description="Mastercard number",
|
|
107
|
+
default_action=ActionType.REDACT,
|
|
108
|
+
tags=["pci", "card", "mastercard"],
|
|
109
|
+
),
|
|
110
|
+
PatternDefinition(
|
|
111
|
+
name="mastercard_2_series",
|
|
112
|
+
regex=r"\b2[2-7][0-9]{2}[\s-]?[0-9]{4}[\s-]?[0-9]{4}[\s-]?[0-9]{4}\b",
|
|
113
|
+
severity=Severity.CRITICAL,
|
|
114
|
+
description="Mastercard 2-series number",
|
|
115
|
+
default_action=ActionType.REDACT,
|
|
116
|
+
tags=["pci", "card", "mastercard"],
|
|
117
|
+
),
|
|
118
|
+
PatternDefinition(
|
|
119
|
+
name="amex",
|
|
120
|
+
regex=r"\b3[47][0-9]{2}[\s-]?[0-9]{6}[\s-]?[0-9]{5}\b",
|
|
121
|
+
severity=Severity.CRITICAL,
|
|
122
|
+
description="American Express card number",
|
|
123
|
+
default_action=ActionType.REDACT,
|
|
124
|
+
tags=["pci", "card", "amex"],
|
|
125
|
+
),
|
|
126
|
+
PatternDefinition(
|
|
127
|
+
name="discover",
|
|
128
|
+
regex=r"\b6(?:011|5[0-9]{2})[\s-]?[0-9]{4}[\s-]?[0-9]{4}[\s-]?[0-9]{4}\b",
|
|
129
|
+
severity=Severity.CRITICAL,
|
|
130
|
+
description="Discover card number",
|
|
131
|
+
default_action=ActionType.REDACT,
|
|
132
|
+
tags=["pci", "card", "discover"],
|
|
133
|
+
),
|
|
134
|
+
PatternDefinition(
|
|
135
|
+
name="diners",
|
|
136
|
+
regex=r"\b3(?:0[0-5]|[68][0-9])[0-9][\s-]?[0-9]{6}[\s-]?[0-9]{4}\b",
|
|
137
|
+
severity=Severity.CRITICAL,
|
|
138
|
+
description="Diners Club card number",
|
|
139
|
+
default_action=ActionType.REDACT,
|
|
140
|
+
tags=["pci", "card", "diners"],
|
|
141
|
+
),
|
|
142
|
+
PatternDefinition(
|
|
143
|
+
name="jcb",
|
|
144
|
+
regex=r"\b(?:2131|1800|35[0-9]{2})[\s-]?[0-9]{4}[\s-]?[0-9]{4}[\s-]?[0-9]{4}\b",
|
|
145
|
+
severity=Severity.CRITICAL,
|
|
146
|
+
description="JCB card number",
|
|
147
|
+
default_action=ActionType.REDACT,
|
|
148
|
+
tags=["pci", "card", "jcb"],
|
|
149
|
+
),
|
|
150
|
+
PatternDefinition(
|
|
151
|
+
name="generic_card",
|
|
152
|
+
regex=r"\b(?:[0-9]{4}[\s-]?){3}[0-9]{4}\b",
|
|
153
|
+
severity=Severity.HIGH,
|
|
154
|
+
description="Potential card number (16 digits)",
|
|
155
|
+
default_action=ActionType.WARN,
|
|
156
|
+
tags=["pci", "card", "generic"],
|
|
157
|
+
),
|
|
158
|
+
|
|
159
|
+
# =================================================================
|
|
160
|
+
# CVV/CVC/Security Codes
|
|
161
|
+
# =================================================================
|
|
162
|
+
PatternDefinition(
|
|
163
|
+
name="cvv_labeled",
|
|
164
|
+
regex=r"(?i)(?:CVV|CVC|CVV2|CVC2|CSC|CID)[:\s#]*[0-9]{3,4}",
|
|
165
|
+
severity=Severity.CRITICAL,
|
|
166
|
+
description="Card security code (CVV/CVC)",
|
|
167
|
+
default_action=ActionType.REDACT,
|
|
168
|
+
tags=["pci", "cvv"],
|
|
169
|
+
),
|
|
170
|
+
PatternDefinition(
|
|
171
|
+
name="security_code",
|
|
172
|
+
regex=r"(?i)(?:SECURITY\s*CODE|CARD\s*CODE|VERIFICATION\s*(?:CODE|VALUE))[:\s#]*[0-9]{3,4}",
|
|
173
|
+
severity=Severity.CRITICAL,
|
|
174
|
+
description="Card security/verification code",
|
|
175
|
+
default_action=ActionType.REDACT,
|
|
176
|
+
tags=["pci", "cvv"],
|
|
177
|
+
),
|
|
178
|
+
|
|
179
|
+
# =================================================================
|
|
180
|
+
# Expiration Dates
|
|
181
|
+
# =================================================================
|
|
182
|
+
PatternDefinition(
|
|
183
|
+
name="expiry_labeled",
|
|
184
|
+
regex=r"(?i)(?:EXP(?:IRY|IRATION)?|VALID\s*(?:THRU|UNTIL))[:\s]*(?:0[1-9]|1[0-2])[\s/\-]?(?:[0-9]{2}|20[2-9][0-9])",
|
|
185
|
+
severity=Severity.HIGH,
|
|
186
|
+
description="Card expiration date",
|
|
187
|
+
default_action=ActionType.WARN,
|
|
188
|
+
tags=["pci", "expiry"],
|
|
189
|
+
),
|
|
190
|
+
|
|
191
|
+
# =================================================================
|
|
192
|
+
# Bank Account Information
|
|
193
|
+
# =================================================================
|
|
194
|
+
PatternDefinition(
|
|
195
|
+
name="bank_account",
|
|
196
|
+
regex=r"(?i)(?:BANK\s*)?(?:ACCOUNT|ACCT)[\s#:]*[0-9]{8,17}",
|
|
197
|
+
severity=Severity.HIGH,
|
|
198
|
+
description="Bank account number",
|
|
199
|
+
default_action=ActionType.WARN,
|
|
200
|
+
tags=["pci", "bank", "account"],
|
|
201
|
+
),
|
|
202
|
+
PatternDefinition(
|
|
203
|
+
name="routing_number",
|
|
204
|
+
regex=r"(?i)(?:ROUTING|ABA|RTN)[\s#:]*[0-9]{9}",
|
|
205
|
+
severity=Severity.HIGH,
|
|
206
|
+
description="Bank routing number",
|
|
207
|
+
default_action=ActionType.WARN,
|
|
208
|
+
tags=["pci", "bank", "routing"],
|
|
209
|
+
),
|
|
210
|
+
PatternDefinition(
|
|
211
|
+
name="iban",
|
|
212
|
+
regex=r"\b[A-Z]{2}[0-9]{2}[A-Z0-9]{4}[0-9]{7}(?:[A-Z0-9]?){0,16}\b",
|
|
213
|
+
severity=Severity.HIGH,
|
|
214
|
+
description="International Bank Account Number (IBAN)",
|
|
215
|
+
default_action=ActionType.WARN,
|
|
216
|
+
tags=["pci", "bank", "iban"],
|
|
217
|
+
),
|
|
218
|
+
PatternDefinition(
|
|
219
|
+
name="swift_bic",
|
|
220
|
+
regex=r"(?i)(?:SWIFT|BIC)[:\s]*[A-Z]{6}[A-Z0-9]{2}(?:[A-Z0-9]{3})?",
|
|
221
|
+
severity=Severity.MEDIUM,
|
|
222
|
+
description="SWIFT/BIC code",
|
|
223
|
+
default_action=ActionType.WARN,
|
|
224
|
+
tags=["pci", "bank", "swift"],
|
|
225
|
+
),
|
|
226
|
+
|
|
227
|
+
# =================================================================
|
|
228
|
+
# Payment Tokens and References
|
|
229
|
+
# =================================================================
|
|
230
|
+
PatternDefinition(
|
|
231
|
+
name="stripe_token",
|
|
232
|
+
regex=r"(?i)(?:tok|pm|pi|ch|cus|sub|inv|in)_[a-zA-Z0-9]{14,}",
|
|
233
|
+
severity=Severity.MEDIUM,
|
|
234
|
+
description="Stripe payment token/ID",
|
|
235
|
+
default_action=ActionType.WARN,
|
|
236
|
+
tags=["pci", "token", "stripe"],
|
|
237
|
+
),
|
|
238
|
+
PatternDefinition(
|
|
239
|
+
name="paypal_transaction",
|
|
240
|
+
regex=r"(?i)(?:PAYPAL|PP)\s*(?:TRANSACTION|TXN|ID)[:\s#]*[A-Z0-9]{17}",
|
|
241
|
+
severity=Severity.MEDIUM,
|
|
242
|
+
description="PayPal transaction ID",
|
|
243
|
+
default_action=ActionType.WARN,
|
|
244
|
+
tags=["pci", "token", "paypal"],
|
|
245
|
+
),
|
|
246
|
+
|
|
247
|
+
# =================================================================
|
|
248
|
+
# Cardholder Data
|
|
249
|
+
# =================================================================
|
|
250
|
+
PatternDefinition(
|
|
251
|
+
name="cardholder_name",
|
|
252
|
+
regex=r"(?i)(?:CARDHOLDER|CARD\s*HOLDER|NAME\s*ON\s*CARD)[:\s]+[A-Z][a-z]+(?:\s+[A-Z][a-z]+)+",
|
|
253
|
+
severity=Severity.MEDIUM,
|
|
254
|
+
description="Cardholder name",
|
|
255
|
+
default_action=ActionType.WARN,
|
|
256
|
+
tags=["pci", "cardholder"],
|
|
257
|
+
),
|
|
258
|
+
PatternDefinition(
|
|
259
|
+
name="billing_address",
|
|
260
|
+
regex=r"(?i)(?:BILLING\s*ADDRESS|CARD\s*ADDRESS)[:\s]+[\w\s,.-]+",
|
|
261
|
+
severity=Severity.LOW,
|
|
262
|
+
description="Billing address",
|
|
263
|
+
default_action=ActionType.WARN,
|
|
264
|
+
tags=["pci", "cardholder", "address"],
|
|
265
|
+
),
|
|
266
|
+
|
|
267
|
+
# =================================================================
|
|
268
|
+
# PCI Context Markers
|
|
269
|
+
# =================================================================
|
|
270
|
+
PatternDefinition(
|
|
271
|
+
name="pan_marker",
|
|
272
|
+
regex=r"(?i)\bPAN\b[:\s]+|PRIMARY\s*ACCOUNT\s*NUMBER",
|
|
273
|
+
severity=Severity.HIGH,
|
|
274
|
+
description="Primary Account Number reference",
|
|
275
|
+
default_action=ActionType.WARN,
|
|
276
|
+
tags=["pci", "marker"],
|
|
277
|
+
),
|
|
278
|
+
PatternDefinition(
|
|
279
|
+
name="magnetic_stripe",
|
|
280
|
+
regex=r"(?i)(?:TRACK\s*[12]|MAGNETIC\s*STRIPE|MAG\s*STRIPE)\s*DATA",
|
|
281
|
+
severity=Severity.CRITICAL,
|
|
282
|
+
description="Magnetic stripe data reference",
|
|
283
|
+
default_action=ActionType.BLOCK,
|
|
284
|
+
tags=["pci", "magnetic", "sensitive"],
|
|
285
|
+
),
|
|
286
|
+
PatternDefinition(
|
|
287
|
+
name="pin_block",
|
|
288
|
+
regex=r"(?i)PIN\s*BLOCK|ENCRYPTED\s*PIN",
|
|
289
|
+
severity=Severity.CRITICAL,
|
|
290
|
+
description="PIN block reference",
|
|
291
|
+
default_action=ActionType.BLOCK,
|
|
292
|
+
tags=["pci", "pin", "sensitive"],
|
|
293
|
+
),
|
|
294
|
+
]
|
|
295
|
+
|
|
296
|
+
return self._patterns
|
|
297
|
+
|
|
298
|
+
def scan(self, content: str, direction: ScanDirection) -> "ScanResult":
|
|
299
|
+
"""
|
|
300
|
+
Scan content for PCI data with Luhn validation for card numbers.
|
|
301
|
+
|
|
302
|
+
Overrides base scan to add Luhn validation for card numbers.
|
|
303
|
+
"""
|
|
304
|
+
from tweek.plugins.base import ScanResult
|
|
305
|
+
|
|
306
|
+
result = super().scan(content, direction)
|
|
307
|
+
|
|
308
|
+
if not self._validate_luhn:
|
|
309
|
+
return result
|
|
310
|
+
|
|
311
|
+
# Filter card findings through Luhn validation
|
|
312
|
+
validated_findings = []
|
|
313
|
+
for finding in result.findings:
|
|
314
|
+
if finding.pattern_name in (
|
|
315
|
+
"visa", "mastercard", "mastercard_2_series", "amex",
|
|
316
|
+
"discover", "diners", "jcb", "generic_card"
|
|
317
|
+
):
|
|
318
|
+
# Validate with Luhn
|
|
319
|
+
if luhn_checksum(finding.matched_text):
|
|
320
|
+
validated_findings.append(finding)
|
|
321
|
+
# else: skip false positive
|
|
322
|
+
else:
|
|
323
|
+
# Non-card patterns pass through
|
|
324
|
+
validated_findings.append(finding)
|
|
325
|
+
|
|
326
|
+
return ScanResult(
|
|
327
|
+
passed=len(validated_findings) == 0,
|
|
328
|
+
findings=validated_findings,
|
|
329
|
+
action=self._determine_action(validated_findings),
|
|
330
|
+
message=self._format_message(validated_findings, direction),
|
|
331
|
+
scan_direction=direction,
|
|
332
|
+
plugin_name=self.name
|
|
333
|
+
)
|
|
334
|
+
|
|
335
|
+
def _format_message(
|
|
336
|
+
self,
|
|
337
|
+
findings: List,
|
|
338
|
+
direction: ScanDirection
|
|
339
|
+
) -> Optional[str]:
|
|
340
|
+
"""Format a PCI-specific message."""
|
|
341
|
+
if not findings:
|
|
342
|
+
return None
|
|
343
|
+
|
|
344
|
+
card_findings = [f for f in findings if "card" in f.metadata.get("pattern_tags", [])]
|
|
345
|
+
cvv_findings = [f for f in findings if "cvv" in f.metadata.get("pattern_tags", [])]
|
|
346
|
+
|
|
347
|
+
msg_parts = []
|
|
348
|
+
|
|
349
|
+
if direction == ScanDirection.OUTPUT:
|
|
350
|
+
msg_parts.append(f"WARNING: LLM output contains {len(findings)} potential PCI data element(s).")
|
|
351
|
+
else:
|
|
352
|
+
msg_parts.append(f"ALERT: Input contains {len(findings)} potential PCI data element(s).")
|
|
353
|
+
|
|
354
|
+
if card_findings:
|
|
355
|
+
msg_parts.append(f" {len(card_findings)} card number(s) detected (Luhn validated)")
|
|
356
|
+
if cvv_findings:
|
|
357
|
+
msg_parts.append(f" {len(cvv_findings)} CVV/security code(s) detected")
|
|
358
|
+
|
|
359
|
+
msg_parts.append("This data must be protected per PCI-DSS requirements.")
|
|
360
|
+
|
|
361
|
+
return "\n".join(msg_parts)
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Tweek SOC2 Compliance Plugin
|
|
4
|
+
|
|
5
|
+
Detects patterns indicating SOC2-relevant security and compliance concerns:
|
|
6
|
+
- Access control indicators (passwords, API keys in logs)
|
|
7
|
+
- Audit log patterns (security events, access logs)
|
|
8
|
+
- Change management markers
|
|
9
|
+
- Incident response indicators
|
|
10
|
+
- Risk assessment markers
|
|
11
|
+
- Security policy references
|
|
12
|
+
|
|
13
|
+
SOC2 Trust Services Criteria:
|
|
14
|
+
- Security
|
|
15
|
+
- Availability
|
|
16
|
+
- Processing Integrity
|
|
17
|
+
- Confidentiality
|
|
18
|
+
- Privacy
|
|
19
|
+
|
|
20
|
+
Supports bidirectional scanning:
|
|
21
|
+
- OUTPUT: Detect LLM generating content that could violate SOC2 controls
|
|
22
|
+
- INPUT: Detect sensitive SOC2-relevant data in incoming content
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
from typing import Optional, List, Dict, Any
|
|
26
|
+
from tweek.plugins.base import (
|
|
27
|
+
CompliancePlugin,
|
|
28
|
+
ScanDirection,
|
|
29
|
+
ActionType,
|
|
30
|
+
Severity,
|
|
31
|
+
PatternDefinition,
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class SOC2CompliancePlugin(CompliancePlugin):
|
|
36
|
+
"""
|
|
37
|
+
SOC2 compliance plugin.
|
|
38
|
+
|
|
39
|
+
Detects patterns relevant to SOC2 Trust Services Criteria,
|
|
40
|
+
helping prevent exposure of security-sensitive information.
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
VERSION = "1.0.0"
|
|
44
|
+
DESCRIPTION = "Detect SOC2-relevant security and compliance patterns"
|
|
45
|
+
AUTHOR = "Tweek"
|
|
46
|
+
REQUIRES_LICENSE = "enterprise"
|
|
47
|
+
TAGS = ["compliance", "soc2", "security", "audit"]
|
|
48
|
+
|
|
49
|
+
def __init__(self, config: Optional[Dict[str, Any]] = None):
|
|
50
|
+
super().__init__(config)
|
|
51
|
+
self._patterns: Optional[List[PatternDefinition]] = None
|
|
52
|
+
|
|
53
|
+
@property
|
|
54
|
+
def name(self) -> str:
|
|
55
|
+
return "soc2"
|
|
56
|
+
|
|
57
|
+
@property
|
|
58
|
+
def scan_direction(self) -> ScanDirection:
|
|
59
|
+
direction = self._config.get("scan_direction", "both")
|
|
60
|
+
return ScanDirection(direction)
|
|
61
|
+
|
|
62
|
+
def get_patterns(self) -> List[PatternDefinition]:
|
|
63
|
+
"""Return SOC2 compliance patterns."""
|
|
64
|
+
if self._patterns is not None:
|
|
65
|
+
return self._patterns
|
|
66
|
+
|
|
67
|
+
self._patterns = [
|
|
68
|
+
# =================================================================
|
|
69
|
+
# Access Control (Security Criteria)
|
|
70
|
+
# =================================================================
|
|
71
|
+
PatternDefinition(
|
|
72
|
+
name="access_credentials",
|
|
73
|
+
regex=r"(?i)(?:admin|root|service)\s*(?:password|pwd|pass|credential)[:\s=]+\S+",
|
|
74
|
+
severity=Severity.CRITICAL,
|
|
75
|
+
description="Administrative credentials exposure",
|
|
76
|
+
default_action=ActionType.BLOCK,
|
|
77
|
+
tags=["soc2", "security", "access-control"],
|
|
78
|
+
),
|
|
79
|
+
PatternDefinition(
|
|
80
|
+
name="api_key_exposure",
|
|
81
|
+
regex=r"(?i)(?:api[_-]?key|auth[_-]?token|bearer[_-]?token|access[_-]?token)[:\s=]+['\"]?[\w-]{20,}['\"]?",
|
|
82
|
+
severity=Severity.CRITICAL,
|
|
83
|
+
description="API key or token exposure",
|
|
84
|
+
default_action=ActionType.REDACT,
|
|
85
|
+
tags=["soc2", "security", "access-control"],
|
|
86
|
+
),
|
|
87
|
+
PatternDefinition(
|
|
88
|
+
name="connection_string",
|
|
89
|
+
regex=r"(?i)(?:connection[_-]?string|conn[_-]?str)[:\s=]+['\"]?[^'\"]{20,}['\"]?",
|
|
90
|
+
severity=Severity.HIGH,
|
|
91
|
+
description="Database connection string exposure",
|
|
92
|
+
default_action=ActionType.REDACT,
|
|
93
|
+
tags=["soc2", "security", "access-control"],
|
|
94
|
+
),
|
|
95
|
+
PatternDefinition(
|
|
96
|
+
name="private_key_header",
|
|
97
|
+
regex=r"-----BEGIN\s+(?:RSA\s+)?PRIVATE\s+KEY-----",
|
|
98
|
+
severity=Severity.CRITICAL,
|
|
99
|
+
description="Private key header detected",
|
|
100
|
+
default_action=ActionType.BLOCK,
|
|
101
|
+
tags=["soc2", "security", "cryptography"],
|
|
102
|
+
),
|
|
103
|
+
|
|
104
|
+
# =================================================================
|
|
105
|
+
# Audit Logging (Security/Processing Integrity)
|
|
106
|
+
# =================================================================
|
|
107
|
+
PatternDefinition(
|
|
108
|
+
name="audit_log_tampering",
|
|
109
|
+
regex=r"(?i)(?:delete|truncate|drop)\s+(?:from\s+)?(?:audit|security|access)[_-]?logs?",
|
|
110
|
+
severity=Severity.CRITICAL,
|
|
111
|
+
description="Audit log tampering attempt",
|
|
112
|
+
default_action=ActionType.BLOCK,
|
|
113
|
+
tags=["soc2", "audit", "integrity"],
|
|
114
|
+
),
|
|
115
|
+
PatternDefinition(
|
|
116
|
+
name="log_manipulation",
|
|
117
|
+
regex=r"(?i)(?:modify|alter|update)\s+(?:audit|security)[_-]?(?:log|trail|record)",
|
|
118
|
+
severity=Severity.HIGH,
|
|
119
|
+
description="Security log manipulation",
|
|
120
|
+
default_action=ActionType.WARN,
|
|
121
|
+
tags=["soc2", "audit", "integrity"],
|
|
122
|
+
),
|
|
123
|
+
PatternDefinition(
|
|
124
|
+
name="disable_logging",
|
|
125
|
+
regex=r"(?i)(?:disable|stop|pause|turn\s*off)\s+(?:audit|security|access)[_-]?log",
|
|
126
|
+
severity=Severity.HIGH,
|
|
127
|
+
description="Attempt to disable security logging",
|
|
128
|
+
default_action=ActionType.WARN,
|
|
129
|
+
tags=["soc2", "audit"],
|
|
130
|
+
),
|
|
131
|
+
|
|
132
|
+
# =================================================================
|
|
133
|
+
# Change Management (Processing Integrity)
|
|
134
|
+
# =================================================================
|
|
135
|
+
PatternDefinition(
|
|
136
|
+
name="unauthorized_change",
|
|
137
|
+
regex=r"(?i)(?:bypass|skip|disable)\s+(?:change[_-]?management|approval|review)",
|
|
138
|
+
severity=Severity.HIGH,
|
|
139
|
+
description="Change management bypass attempt",
|
|
140
|
+
default_action=ActionType.WARN,
|
|
141
|
+
tags=["soc2", "change-management"],
|
|
142
|
+
),
|
|
143
|
+
PatternDefinition(
|
|
144
|
+
name="production_direct_access",
|
|
145
|
+
regex=r"(?i)direct\s+(?:production|prod)\s+(?:access|modification|change)",
|
|
146
|
+
severity=Severity.MEDIUM,
|
|
147
|
+
description="Direct production access indication",
|
|
148
|
+
default_action=ActionType.WARN,
|
|
149
|
+
tags=["soc2", "change-management"],
|
|
150
|
+
),
|
|
151
|
+
|
|
152
|
+
# =================================================================
|
|
153
|
+
# Incident Response (Security/Availability)
|
|
154
|
+
# =================================================================
|
|
155
|
+
PatternDefinition(
|
|
156
|
+
name="security_incident",
|
|
157
|
+
regex=r"(?i)(?:security\s+)?(?:incident|breach|compromise|intrusion)\s+(?:report|detected|confirmed)",
|
|
158
|
+
severity=Severity.HIGH,
|
|
159
|
+
description="Security incident indicator",
|
|
160
|
+
default_action=ActionType.WARN,
|
|
161
|
+
tags=["soc2", "incident-response"],
|
|
162
|
+
),
|
|
163
|
+
PatternDefinition(
|
|
164
|
+
name="data_breach",
|
|
165
|
+
regex=r"(?i)data\s+(?:breach|leak|exposure|exfiltration)",
|
|
166
|
+
severity=Severity.CRITICAL,
|
|
167
|
+
description="Data breach indicator",
|
|
168
|
+
default_action=ActionType.WARN,
|
|
169
|
+
tags=["soc2", "incident-response", "confidentiality"],
|
|
170
|
+
),
|
|
171
|
+
PatternDefinition(
|
|
172
|
+
name="unauthorized_access",
|
|
173
|
+
regex=r"(?i)unauthorized\s+(?:access|login|entry|attempt)",
|
|
174
|
+
severity=Severity.HIGH,
|
|
175
|
+
description="Unauthorized access indicator",
|
|
176
|
+
default_action=ActionType.WARN,
|
|
177
|
+
tags=["soc2", "incident-response", "security"],
|
|
178
|
+
),
|
|
179
|
+
|
|
180
|
+
# =================================================================
|
|
181
|
+
# Confidentiality Controls
|
|
182
|
+
# =================================================================
|
|
183
|
+
PatternDefinition(
|
|
184
|
+
name="confidential_marker",
|
|
185
|
+
regex=r"(?i)\[?\s*(?:CONFIDENTIAL|INTERNAL\s+ONLY|RESTRICTED)\s*\]?",
|
|
186
|
+
severity=Severity.MEDIUM,
|
|
187
|
+
description="Confidentiality marking",
|
|
188
|
+
default_action=ActionType.WARN,
|
|
189
|
+
tags=["soc2", "confidentiality"],
|
|
190
|
+
),
|
|
191
|
+
PatternDefinition(
|
|
192
|
+
name="customer_data_exposure",
|
|
193
|
+
regex=r"(?i)(?:customer|client|user)\s+(?:pii|personal\s+data|sensitive\s+data)",
|
|
194
|
+
severity=Severity.HIGH,
|
|
195
|
+
description="Customer sensitive data reference",
|
|
196
|
+
default_action=ActionType.WARN,
|
|
197
|
+
tags=["soc2", "confidentiality", "privacy"],
|
|
198
|
+
),
|
|
199
|
+
|
|
200
|
+
# =================================================================
|
|
201
|
+
# Risk Assessment
|
|
202
|
+
# =================================================================
|
|
203
|
+
PatternDefinition(
|
|
204
|
+
name="vulnerability_disclosure",
|
|
205
|
+
regex=r"(?i)(?:vulnerability|cve-\d{4}-\d+|security\s+flaw)\s+(?:in|affecting|found)",
|
|
206
|
+
severity=Severity.MEDIUM,
|
|
207
|
+
description="Vulnerability disclosure",
|
|
208
|
+
default_action=ActionType.WARN,
|
|
209
|
+
tags=["soc2", "risk", "security"],
|
|
210
|
+
),
|
|
211
|
+
PatternDefinition(
|
|
212
|
+
name="risk_assessment",
|
|
213
|
+
regex=r"(?i)(?:high|critical)\s+risk\s+(?:identified|assessment|finding)",
|
|
214
|
+
severity=Severity.MEDIUM,
|
|
215
|
+
description="High risk assessment finding",
|
|
216
|
+
default_action=ActionType.WARN,
|
|
217
|
+
tags=["soc2", "risk"],
|
|
218
|
+
),
|
|
219
|
+
|
|
220
|
+
# =================================================================
|
|
221
|
+
# Security Policy
|
|
222
|
+
# =================================================================
|
|
223
|
+
PatternDefinition(
|
|
224
|
+
name="policy_exception",
|
|
225
|
+
regex=r"(?i)(?:security\s+)?policy\s+exception\s+(?:granted|approved|requested)",
|
|
226
|
+
severity=Severity.MEDIUM,
|
|
227
|
+
description="Security policy exception",
|
|
228
|
+
default_action=ActionType.WARN,
|
|
229
|
+
tags=["soc2", "policy"],
|
|
230
|
+
),
|
|
231
|
+
PatternDefinition(
|
|
232
|
+
name="compliance_violation",
|
|
233
|
+
regex=r"(?i)(?:compliance|regulatory|policy)\s+violation",
|
|
234
|
+
severity=Severity.HIGH,
|
|
235
|
+
description="Compliance violation indicator",
|
|
236
|
+
default_action=ActionType.WARN,
|
|
237
|
+
tags=["soc2", "compliance"],
|
|
238
|
+
),
|
|
239
|
+
]
|
|
240
|
+
|
|
241
|
+
return self._patterns
|
|
242
|
+
|
|
243
|
+
def _format_message(
|
|
244
|
+
self,
|
|
245
|
+
findings: List,
|
|
246
|
+
direction: ScanDirection
|
|
247
|
+
) -> Optional[str]:
|
|
248
|
+
"""Format a SOC2-specific message."""
|
|
249
|
+
if not findings:
|
|
250
|
+
return None
|
|
251
|
+
|
|
252
|
+
# Group findings by SOC2 criteria
|
|
253
|
+
security = [f for f in findings if "security" in f.metadata.get("pattern_tags", [])]
|
|
254
|
+
confidentiality = [f for f in findings if "confidentiality" in f.metadata.get("pattern_tags", [])]
|
|
255
|
+
audit = [f for f in findings if "audit" in f.metadata.get("pattern_tags", [])]
|
|
256
|
+
|
|
257
|
+
if direction == ScanDirection.OUTPUT:
|
|
258
|
+
lines = [
|
|
259
|
+
f"WARNING: LLM output contains {len(findings)} SOC2-relevant finding(s).",
|
|
260
|
+
"Review for potential security/compliance concerns:"
|
|
261
|
+
]
|
|
262
|
+
else:
|
|
263
|
+
lines = [
|
|
264
|
+
f"ALERT: Input contains {len(findings)} SOC2-relevant finding(s).",
|
|
265
|
+
"Verify proper handling according to security policies:"
|
|
266
|
+
]
|
|
267
|
+
|
|
268
|
+
if security:
|
|
269
|
+
lines.append(f" Security: {len(security)} finding(s)")
|
|
270
|
+
if confidentiality:
|
|
271
|
+
lines.append(f" Confidentiality: {len(confidentiality)} finding(s)")
|
|
272
|
+
if audit:
|
|
273
|
+
lines.append(f" Audit: {len(audit)} finding(s)")
|
|
274
|
+
|
|
275
|
+
return "\n".join(lines)
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Tweek Tool Detector Plugins
|
|
4
|
+
|
|
5
|
+
Detector plugins identify installed LLM tools and IDEs:
|
|
6
|
+
- Moltbot: AI coding assistant
|
|
7
|
+
- Cursor: AI-powered IDE
|
|
8
|
+
- Continue.dev: VS Code AI extension
|
|
9
|
+
- Copilot: GitHub Copilot
|
|
10
|
+
- Windsurf: Codeium AI IDE
|
|
11
|
+
|
|
12
|
+
Detection helps:
|
|
13
|
+
- Identify proxy conflicts
|
|
14
|
+
- Configure appropriate protection
|
|
15
|
+
- Suggest integration options
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from tweek.plugins.detectors.moltbot import MoltbotDetector
|
|
19
|
+
from tweek.plugins.detectors.cursor import CursorDetector
|
|
20
|
+
from tweek.plugins.detectors.continue_dev import ContinueDetector
|
|
21
|
+
from tweek.plugins.detectors.copilot import CopilotDetector
|
|
22
|
+
from tweek.plugins.detectors.windsurf import WindsurfDetector
|
|
23
|
+
|
|
24
|
+
__all__ = [
|
|
25
|
+
"MoltbotDetector",
|
|
26
|
+
"CursorDetector",
|
|
27
|
+
"ContinueDetector",
|
|
28
|
+
"CopilotDetector",
|
|
29
|
+
"WindsurfDetector",
|
|
30
|
+
]
|