diffsense 2.2.12__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.
- adapters/__init__.py +0 -0
- adapters/base.py +27 -0
- adapters/github_adapter.py +164 -0
- adapters/gitlab_adapter.py +207 -0
- adapters/local_adapter.py +136 -0
- banner.py +71 -0
- cli.py +606 -0
- config/__init__.py +1 -0
- config/rules.yaml +371 -0
- core/__init__.py +235 -0
- core/ast_detector.py +853 -0
- core/change.py +46 -0
- core/composer.py +93 -0
- core/evaluator.py +15 -0
- core/ignore_manager.py +71 -0
- core/knowledge.py +77 -0
- core/parser.py +181 -0
- core/parser_manager.py +104 -0
- core/quality_manager.py +117 -0
- core/renderer.py +197 -0
- core/rule_base.py +98 -0
- core/rule_runtime.py +103 -0
- core/rules.py +718 -0
- core/run_config.py +85 -0
- core/semantic_diff.py +359 -0
- core/signal_model.py +21 -0
- core/signals_registry.py +62 -0
- diffsense-2.2.12.dist-info/METADATA +18 -0
- diffsense-2.2.12.dist-info/RECORD +58 -0
- diffsense-2.2.12.dist-info/WHEEL +5 -0
- diffsense-2.2.12.dist-info/entry_points.txt +3 -0
- diffsense-2.2.12.dist-info/licenses/LICENSE +176 -0
- diffsense-2.2.12.dist-info/top_level.txt +11 -0
- diffsense_mcp/__init__.py +1 -0
- diffsense_mcp/launcher.py +28 -0
- diffsense_mcp/server.py +687 -0
- governance/lifecycle.py +54 -0
- main.py +318 -0
- rules/__init__.py +246 -0
- rules/api_compatibility.py +372 -0
- rules/collection_handling.py +349 -0
- rules/concurrency.py +194 -0
- rules/concurrency_adapter.py +250 -0
- rules/cross_language_adapter.py +444 -0
- rules/exception_handling.py +320 -0
- rules/go_rules.py +401 -0
- rules/null_safety.py +301 -0
- rules/resource_management.py +222 -0
- rules/yaml_adapter.py +195 -0
- run_audit.py +478 -0
- sdk/cpp_adapter.py +238 -0
- sdk/go_adapter.py +199 -0
- sdk/java_adapter.py +199 -0
- sdk/javascript_adapter.py +229 -0
- sdk/language_adapter.py +313 -0
- sdk/python_adapter.py +195 -0
- sdk/rule.py +63 -0
- sdk/signal.py +14 -0
sdk/python_adapter.py
ADDED
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Python Language Adapter for DiffSense
|
|
3
|
+
|
|
4
|
+
Provides Python-specific patterns and constructs for rule development.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import re
|
|
8
|
+
from typing import List, Set, Pattern, Dict
|
|
9
|
+
from .language_adapter import LanguageAdapter
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class PythonAdapter(LanguageAdapter):
|
|
13
|
+
"""
|
|
14
|
+
Language adapter for Python.
|
|
15
|
+
|
|
16
|
+
Provides Python-specific patterns for concurrency, resource management,
|
|
17
|
+
error handling, and other language constructs.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
def __init__(self):
|
|
21
|
+
super().__init__("python")
|
|
22
|
+
self._compile_patterns()
|
|
23
|
+
|
|
24
|
+
def _compile_patterns(self):
|
|
25
|
+
"""Pre-compile commonly used patterns."""
|
|
26
|
+
# Thread Safety / Concurrency Patterns
|
|
27
|
+
self._lock_patterns = [
|
|
28
|
+
re.compile(r'threading\.Lock\(\)'),
|
|
29
|
+
re.compile(r'threading\.RLock\(\)'),
|
|
30
|
+
re.compile(r'with\s+self\._lock:'),
|
|
31
|
+
re.compile(r'with\s+self\.lock:'),
|
|
32
|
+
re.compile(r'with\s+lock:'),
|
|
33
|
+
]
|
|
34
|
+
|
|
35
|
+
self._unlock_patterns = [
|
|
36
|
+
re.compile(r'\.release\(\)'),
|
|
37
|
+
]
|
|
38
|
+
|
|
39
|
+
self._volatile_patterns = [
|
|
40
|
+
re.compile(r'from\s+threading\s+import\s+Event'),
|
|
41
|
+
re.compile(r'threading\.Event\(\)'),
|
|
42
|
+
]
|
|
43
|
+
|
|
44
|
+
# Resource Management Patterns
|
|
45
|
+
self._resource_creation = [
|
|
46
|
+
re.compile(r'open\('),
|
|
47
|
+
re.compile(r'with\s+open\('),
|
|
48
|
+
re.compile(r'socket\.socket\('),
|
|
49
|
+
re.compile(r'urllib\.request\.urlopen\('),
|
|
50
|
+
re.compile(r'requests\.(?:get|post)\('),
|
|
51
|
+
]
|
|
52
|
+
|
|
53
|
+
# Error Handling Patterns
|
|
54
|
+
self._error_check = [
|
|
55
|
+
re.compile(r'except\s*(?:Exception)?(?:\s+as\s+\w+)?:'),
|
|
56
|
+
re.compile(r'if\s+\w+\s+is\s+None'),
|
|
57
|
+
re.compile(r'if\s+\w+\s+is\s+not\s+None'),
|
|
58
|
+
re.compile(r'if\s+hasattr\('),
|
|
59
|
+
re.compile(r'if\s+hasattr\([^,]+,\s*[\'"]\w+[\'"]\)'),
|
|
60
|
+
]
|
|
61
|
+
|
|
62
|
+
self._error_ignore = [
|
|
63
|
+
re.compile(r'except\s*:\s*$', re.MULTILINE),
|
|
64
|
+
re.compile(r'except\s+Exception:\s*$', re.MULTILINE),
|
|
65
|
+
re.compile(r'pass\s*$', re.MULTILINE),
|
|
66
|
+
]
|
|
67
|
+
|
|
68
|
+
# Thread Creation Patterns
|
|
69
|
+
self._thread_creation = [
|
|
70
|
+
re.compile(r'threading\.Thread\('),
|
|
71
|
+
re.compile(r'with\s+ThreadPoolExecutor'),
|
|
72
|
+
re.compile(r'with\s+ProcessPoolExecutor'),
|
|
73
|
+
re.compile(r'concurrent\.futures\.(?:Thread|Process)PoolExecutor'),
|
|
74
|
+
re.compile(r'asyncio\.create_task\('),
|
|
75
|
+
]
|
|
76
|
+
|
|
77
|
+
# Security Patterns
|
|
78
|
+
self._security = {
|
|
79
|
+
'command_injection': [
|
|
80
|
+
re.compile(r'os\.system\('),
|
|
81
|
+
re.compile(r'os\.popen\('),
|
|
82
|
+
re.compile(r'subprocess\.call\('),
|
|
83
|
+
re.compile(r'subprocess\.run\([^,]+shell\s*=\s*True'),
|
|
84
|
+
re.compile(r'subprocess\.Popen\('),
|
|
85
|
+
],
|
|
86
|
+
'hardcoded_secret': [
|
|
87
|
+
re.compile(r'password\s*=\s*["\'][^"\']+["\']'),
|
|
88
|
+
re.compile(r'api[_-]?key\s*=\s*["\'][^"\']+["\']'),
|
|
89
|
+
re.compile(r'secret\s*=\s*["\'][^"\']+["\']'),
|
|
90
|
+
re.compile(r'token\s*=\s*["\'][^"\']+["\']'),
|
|
91
|
+
re.compile(r'aws[_-]?secret'),
|
|
92
|
+
],
|
|
93
|
+
'sql_injection': [
|
|
94
|
+
re.compile(r'cursor\.execute\([^)]*%[^)]*\)'),
|
|
95
|
+
re.compile(r'cursor\.execute\([^)]*\+[^)]*\)'),
|
|
96
|
+
re.compile(r'f["\']SELECT.*\{'),
|
|
97
|
+
re.compile(r'["\']SELECT.*\%'),
|
|
98
|
+
],
|
|
99
|
+
'deserialization': [
|
|
100
|
+
re.compile(r'pickle\.load\('),
|
|
101
|
+
re.compile(r'yaml\.load\('),
|
|
102
|
+
re.compile(r'yaml\.unsafe_load\('),
|
|
103
|
+
re.compile(r'marshal\.load\('),
|
|
104
|
+
],
|
|
105
|
+
'EvalUsage': [
|
|
106
|
+
re.compile(r'\beval\('),
|
|
107
|
+
re.compile(r'\bexec\('),
|
|
108
|
+
],
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
# ==================== Thread Safety ====================
|
|
112
|
+
|
|
113
|
+
def get_thread_safe_types(self) -> Set[str]:
|
|
114
|
+
return {
|
|
115
|
+
'threading.Lock', 'threading.RLock', 'threading.Semaphore',
|
|
116
|
+
'threading.Event', 'threading.Condition', 'threading.Barrier',
|
|
117
|
+
'queue.Queue', 'collections.Queue',
|
|
118
|
+
'concurrent.futures.ThreadPoolExecutor',
|
|
119
|
+
'asyncio.Lock', 'asyncio.Event', 'asyncio.Condition',
|
|
120
|
+
'multiprocessing.Lock', 'multiprocessing.Manager',
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
def get_unsafe_types(self) -> Set[str]:
|
|
124
|
+
return {
|
|
125
|
+
'list', 'dict', 'set', 'tuple',
|
|
126
|
+
'str', 'int', 'float', 'bool',
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
def get_lock_patterns(self) -> List[Pattern]:
|
|
130
|
+
return self._lock_patterns
|
|
131
|
+
|
|
132
|
+
def get_unlock_patterns(self) -> List[Pattern]:
|
|
133
|
+
return self._unlock_patterns
|
|
134
|
+
|
|
135
|
+
def get_volatile_patterns(self) -> List[Pattern]:
|
|
136
|
+
return self._volatile_patterns
|
|
137
|
+
|
|
138
|
+
# ==================== Resource Management ====================
|
|
139
|
+
|
|
140
|
+
def get_cleanup_keywords(self) -> Set[str]:
|
|
141
|
+
return {
|
|
142
|
+
'with', 'finally', '__exit__', '__aenter__', '__aexit__',
|
|
143
|
+
'close()', '.close()', 'contextmanager'
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
def get_resource_creation_patterns(self) -> List[Pattern]:
|
|
147
|
+
return self._resource_creation
|
|
148
|
+
|
|
149
|
+
# ==================== Error Handling ====================
|
|
150
|
+
|
|
151
|
+
def get_error_types(self) -> Set[str]:
|
|
152
|
+
return {
|
|
153
|
+
'Exception', 'BaseException', 'RuntimeError',
|
|
154
|
+
'ValueError', 'TypeError', 'KeyError', 'IndexError',
|
|
155
|
+
'IOError', 'OSError', 'AttributeError'
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
def get_error_check_patterns(self) -> List[Pattern]:
|
|
159
|
+
return self._error_check
|
|
160
|
+
|
|
161
|
+
def get_error_ignore_patterns(self) -> List[Pattern]:
|
|
162
|
+
return self._error_ignore
|
|
163
|
+
|
|
164
|
+
# ==================== Null Safety ====================
|
|
165
|
+
|
|
166
|
+
def get_null_checks(self) -> Set[str]:
|
|
167
|
+
return {
|
|
168
|
+
'== None', 'is None', 'is not None', '!= None',
|
|
169
|
+
'if x:', 'if not x:', 'if x is not None:',
|
|
170
|
+
'hasattr()', 'getattr()', 'try/except'
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
def get_null_pointer_types(self) -> Set[str]:
|
|
174
|
+
return {
|
|
175
|
+
'object', 'None', 'Any', 'Optional',
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
# ==================== Concurrency Primitives ====================
|
|
179
|
+
|
|
180
|
+
def get_concurrency_primitives(self) -> Set[str]:
|
|
181
|
+
return {
|
|
182
|
+
'threading', 'Thread', 'Lock', 'RLock', 'Semaphore',
|
|
183
|
+
'concurrent.futures', 'ThreadPoolExecutor', 'ProcessPoolExecutor',
|
|
184
|
+
'asyncio', 'async', 'await', 'create_task',
|
|
185
|
+
'multiprocessing', 'Process', 'Queue', 'Manager',
|
|
186
|
+
'queue', 'Queue'
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
def get_thread_creation_patterns(self) -> List[Pattern]:
|
|
190
|
+
return self._thread_creation
|
|
191
|
+
|
|
192
|
+
# ==================== Security ====================
|
|
193
|
+
|
|
194
|
+
def get_dangerous_patterns(self) -> Dict[str, List[Pattern]]:
|
|
195
|
+
return self._security
|
sdk/rule.py
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
from typing import Dict, Any, Optional, List
|
|
3
|
+
from sdk.signal import Signal
|
|
4
|
+
|
|
5
|
+
class BaseRule(ABC):
|
|
6
|
+
"""
|
|
7
|
+
Abstract Base Class for all DiffSense Rules.
|
|
8
|
+
This defines the Plugin Interface (SDK).
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
@property
|
|
12
|
+
@abstractmethod
|
|
13
|
+
def id(self) -> str:
|
|
14
|
+
"""Unique Rule ID (e.g., 'runtime.concurrency.lock_removed')"""
|
|
15
|
+
pass
|
|
16
|
+
|
|
17
|
+
@property
|
|
18
|
+
@abstractmethod
|
|
19
|
+
def severity(self) -> str:
|
|
20
|
+
"""Severity level: critical, high, medium, low"""
|
|
21
|
+
pass
|
|
22
|
+
|
|
23
|
+
@property
|
|
24
|
+
@abstractmethod
|
|
25
|
+
def impact(self) -> str:
|
|
26
|
+
"""Impact dimension: security, runtime, data, maintenance"""
|
|
27
|
+
pass
|
|
28
|
+
|
|
29
|
+
@property
|
|
30
|
+
@abstractmethod
|
|
31
|
+
def rationale(self) -> str:
|
|
32
|
+
"""Explanation of why this rule exists and what risk it prevents"""
|
|
33
|
+
pass
|
|
34
|
+
|
|
35
|
+
@property
|
|
36
|
+
def rule_type(self) -> str:
|
|
37
|
+
"""Rule type: regression (depends on diff history) or absolute (context-independent)"""
|
|
38
|
+
return "absolute"
|
|
39
|
+
|
|
40
|
+
@property
|
|
41
|
+
def is_blocking(self) -> bool:
|
|
42
|
+
"""If True, any hit will force a 'critical' review level and suggested 'block_pr' action"""
|
|
43
|
+
return False
|
|
44
|
+
|
|
45
|
+
@property
|
|
46
|
+
def status(self) -> str:
|
|
47
|
+
"""Lifecycle status: experimental, beta, stable, deprecated, disabled"""
|
|
48
|
+
return "stable"
|
|
49
|
+
|
|
50
|
+
@abstractmethod
|
|
51
|
+
def evaluate(self, diff_data: Dict[str, Any], signals: List[Signal]) -> Optional[Dict[str, Any]]:
|
|
52
|
+
"""
|
|
53
|
+
Execute the rule logic against the diff and signals.
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
diff_data: The raw diff parsing result (Legacy support, prefer signals)
|
|
57
|
+
signals: List of Semantic Signals detected
|
|
58
|
+
|
|
59
|
+
Returns:
|
|
60
|
+
Dict with match details (must contain 'file' key) if matched,
|
|
61
|
+
None if not matched.
|
|
62
|
+
"""
|
|
63
|
+
pass
|
sdk/signal.py
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from typing import Dict, Any, Optional
|
|
3
|
+
|
|
4
|
+
@dataclass
|
|
5
|
+
class Signal:
|
|
6
|
+
"""
|
|
7
|
+
Represents a semantic signal extracted from the diff.
|
|
8
|
+
Rules consume Signals, they do not parse code.
|
|
9
|
+
"""
|
|
10
|
+
id: str # e.g., "runtime.concurrency.lock"
|
|
11
|
+
file: str # File path where signal was found
|
|
12
|
+
line: Optional[int] # Line number (if available)
|
|
13
|
+
action: str # "added", "removed", "changed"
|
|
14
|
+
meta: Dict[str, Any] # Additional context (e.g., variable name, type info)
|