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.
Files changed (58) hide show
  1. adapters/__init__.py +0 -0
  2. adapters/base.py +27 -0
  3. adapters/github_adapter.py +164 -0
  4. adapters/gitlab_adapter.py +207 -0
  5. adapters/local_adapter.py +136 -0
  6. banner.py +71 -0
  7. cli.py +606 -0
  8. config/__init__.py +1 -0
  9. config/rules.yaml +371 -0
  10. core/__init__.py +235 -0
  11. core/ast_detector.py +853 -0
  12. core/change.py +46 -0
  13. core/composer.py +93 -0
  14. core/evaluator.py +15 -0
  15. core/ignore_manager.py +71 -0
  16. core/knowledge.py +77 -0
  17. core/parser.py +181 -0
  18. core/parser_manager.py +104 -0
  19. core/quality_manager.py +117 -0
  20. core/renderer.py +197 -0
  21. core/rule_base.py +98 -0
  22. core/rule_runtime.py +103 -0
  23. core/rules.py +718 -0
  24. core/run_config.py +85 -0
  25. core/semantic_diff.py +359 -0
  26. core/signal_model.py +21 -0
  27. core/signals_registry.py +62 -0
  28. diffsense-2.2.12.dist-info/METADATA +18 -0
  29. diffsense-2.2.12.dist-info/RECORD +58 -0
  30. diffsense-2.2.12.dist-info/WHEEL +5 -0
  31. diffsense-2.2.12.dist-info/entry_points.txt +3 -0
  32. diffsense-2.2.12.dist-info/licenses/LICENSE +176 -0
  33. diffsense-2.2.12.dist-info/top_level.txt +11 -0
  34. diffsense_mcp/__init__.py +1 -0
  35. diffsense_mcp/launcher.py +28 -0
  36. diffsense_mcp/server.py +687 -0
  37. governance/lifecycle.py +54 -0
  38. main.py +318 -0
  39. rules/__init__.py +246 -0
  40. rules/api_compatibility.py +372 -0
  41. rules/collection_handling.py +349 -0
  42. rules/concurrency.py +194 -0
  43. rules/concurrency_adapter.py +250 -0
  44. rules/cross_language_adapter.py +444 -0
  45. rules/exception_handling.py +320 -0
  46. rules/go_rules.py +401 -0
  47. rules/null_safety.py +301 -0
  48. rules/resource_management.py +222 -0
  49. rules/yaml_adapter.py +195 -0
  50. run_audit.py +478 -0
  51. sdk/cpp_adapter.py +238 -0
  52. sdk/go_adapter.py +199 -0
  53. sdk/java_adapter.py +199 -0
  54. sdk/javascript_adapter.py +229 -0
  55. sdk/language_adapter.py +313 -0
  56. sdk/python_adapter.py +195 -0
  57. sdk/rule.py +63 -0
  58. 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)