pybinaryguard 1.0.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.
- pybinaryguard/__init__.py +78 -0
- pybinaryguard/__main__.py +7 -0
- pybinaryguard/_compat/__init__.py +0 -0
- pybinaryguard/agent/__init__.py +63 -0
- pybinaryguard/agent/guard.py +232 -0
- pybinaryguard/agent/recommender.py +283 -0
- pybinaryguard/agent/schema.py +200 -0
- pybinaryguard/agent/simulator.py +430 -0
- pybinaryguard/agent/tool_interface.py +474 -0
- pybinaryguard/analyzers/__init__.py +209 -0
- pybinaryguard/analyzers/base.py +40 -0
- pybinaryguard/analyzers/dependency_analyzer.py +336 -0
- pybinaryguard/analyzers/elf_analyzer.py +754 -0
- pybinaryguard/analyzers/symbol_analyzer.py +280 -0
- pybinaryguard/analyzers/wheel_analyzer.py +308 -0
- pybinaryguard/cli/__init__.py +5 -0
- pybinaryguard/cli/commands.py +414 -0
- pybinaryguard/cli/formatters.py +720 -0
- pybinaryguard/cli/main.py +250 -0
- pybinaryguard/diagnostics/__init__.py +13 -0
- pybinaryguard/diagnostics/explainer.py +356 -0
- pybinaryguard/diagnostics/findings.py +146 -0
- pybinaryguard/diagnostics/suggestions.py +508 -0
- pybinaryguard/frameworks/__init__.py +20 -0
- pybinaryguard/frameworks/onnxruntime.py +214 -0
- pybinaryguard/frameworks/pytorch.py +223 -0
- pybinaryguard/frameworks/tensorflow.py +266 -0
- pybinaryguard/frameworks/tensorrt.py +189 -0
- pybinaryguard/models/__init__.py +19 -0
- pybinaryguard/models/enums.py +102 -0
- pybinaryguard/models/finding.py +109 -0
- pybinaryguard/models/package.py +121 -0
- pybinaryguard/models/system.py +118 -0
- pybinaryguard/plugins/__init__.py +19 -0
- pybinaryguard/plugins/contrib/__init__.py +7 -0
- pybinaryguard/plugins/contrib/gstreamer.py +109 -0
- pybinaryguard/plugins/contrib/jetson.py +385 -0
- pybinaryguard/plugins/contrib/opencv.py +306 -0
- pybinaryguard/plugins/contrib/tensorrt.py +426 -0
- pybinaryguard/plugins/hooks.py +273 -0
- pybinaryguard/plugins/loader.py +190 -0
- pybinaryguard/predictor/__init__.py +23 -0
- pybinaryguard/predictor/dependency_graph.py +226 -0
- pybinaryguard/predictor/linker_simulator.py +161 -0
- pybinaryguard/predictor/predictor.py +144 -0
- pybinaryguard/predictor/resolver.py +282 -0
- pybinaryguard/probes/__init__.py +73 -0
- pybinaryguard/probes/base.py +45 -0
- pybinaryguard/probes/board_probe.py +248 -0
- pybinaryguard/probes/cpu_probe.py +176 -0
- pybinaryguard/probes/glibc_probe.py +215 -0
- pybinaryguard/probes/gpu_probe.py +430 -0
- pybinaryguard/probes/library_probe.py +132 -0
- pybinaryguard/probes/os_probe.py +227 -0
- pybinaryguard/probes/python_probe.py +135 -0
- pybinaryguard/probes/toolchain_probe.py +89 -0
- pybinaryguard/probes/venv_probe.py +111 -0
- pybinaryguard/profiles/__init__.py +12 -0
- pybinaryguard/profiles/engine.py +343 -0
- pybinaryguard/rules/__init__.py +24 -0
- pybinaryguard/rules/base.py +57 -0
- pybinaryguard/rules/builtin/__init__.py +136 -0
- pybinaryguard/rules/builtin/arch_rules.py +86 -0
- pybinaryguard/rules/builtin/board_profile_rules.py +282 -0
- pybinaryguard/rules/builtin/container_rules.py +170 -0
- pybinaryguard/rules/builtin/cpu_rules.py +204 -0
- pybinaryguard/rules/builtin/cuda_rules.py +760 -0
- pybinaryguard/rules/builtin/dependency_rules.py +278 -0
- pybinaryguard/rules/builtin/framework_rules.py +252 -0
- pybinaryguard/rules/builtin/glibc_rules.py +320 -0
- pybinaryguard/rules/builtin/numpy_rules.py +110 -0
- pybinaryguard/rules/builtin/predictive_rules.py +165 -0
- pybinaryguard/rules/builtin/python_abi_rules.py +259 -0
- pybinaryguard/rules/builtin/source_build_rules.py +150 -0
- pybinaryguard/rules/builtin/venv_rules.py +159 -0
- pybinaryguard/rules/engine.py +123 -0
- pybinaryguard/scanner.py +904 -0
- pybinaryguard/scoring/__init__.py +19 -0
- pybinaryguard/scoring/engine.py +416 -0
- pybinaryguard/snapshot/__init__.py +20 -0
- pybinaryguard/snapshot/generator.py +168 -0
- pybinaryguard/snapshot/lockfile.py +152 -0
- pybinaryguard/snapshot/verifier.py +315 -0
- pybinaryguard/validators/__init__.py +7 -0
- pybinaryguard/validators/import_validator.py +231 -0
- pybinaryguard-1.0.0.dist-info/METADATA +876 -0
- pybinaryguard-1.0.0.dist-info/RECORD +91 -0
- pybinaryguard-1.0.0.dist-info/WHEEL +5 -0
- pybinaryguard-1.0.0.dist-info/entry_points.txt +8 -0
- pybinaryguard-1.0.0.dist-info/licenses/LICENSE +21 -0
- pybinaryguard-1.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
"""Dependency conflict detection rules.
|
|
2
|
+
|
|
3
|
+
Reads installed packages' METADATA to detect version conflicts between
|
|
4
|
+
what's actually installed. Unlike pip/Poetry which resolve dependencies
|
|
5
|
+
before install, this catches conflicts that exist right now in the
|
|
6
|
+
live environment — including conflicts caused by manual pip installs,
|
|
7
|
+
conda mixing, or stale venvs.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import os
|
|
13
|
+
import re
|
|
14
|
+
from typing import Dict, List, Optional, Set, Tuple
|
|
15
|
+
|
|
16
|
+
from pybinaryguard.models.enums import Severity
|
|
17
|
+
from pybinaryguard.models.finding import Finding
|
|
18
|
+
from pybinaryguard.models.package import PackageBinaryInfo
|
|
19
|
+
from pybinaryguard.models.system import SystemProfile
|
|
20
|
+
from pybinaryguard.rules.base import Rule
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _parse_version(version_str: str) -> Tuple[int, ...]:
|
|
24
|
+
"""Parse a PEP 440 version into a comparable tuple."""
|
|
25
|
+
# Strip pre/post/dev suffixes for comparison
|
|
26
|
+
clean = re.split(r"[^0-9.]", version_str.strip())[0]
|
|
27
|
+
parts = []
|
|
28
|
+
for part in clean.split("."):
|
|
29
|
+
try:
|
|
30
|
+
parts.append(int(part))
|
|
31
|
+
except ValueError:
|
|
32
|
+
break
|
|
33
|
+
return tuple(parts) if parts else (0,)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _version_matches(installed: str, spec: str) -> bool:
|
|
37
|
+
"""Check if installed version matches a version specifier.
|
|
38
|
+
|
|
39
|
+
Handles ==, >=, <=, !=, >, <, ~=, and bare versions.
|
|
40
|
+
"""
|
|
41
|
+
spec = spec.strip()
|
|
42
|
+
if not spec:
|
|
43
|
+
return True
|
|
44
|
+
|
|
45
|
+
installed_t = _parse_version(installed)
|
|
46
|
+
|
|
47
|
+
# Handle compound specs (>=1.0,<2.0)
|
|
48
|
+
if "," in spec:
|
|
49
|
+
return all(_version_matches(installed, s.strip()) for s in spec.split(","))
|
|
50
|
+
|
|
51
|
+
# Parse operator and version
|
|
52
|
+
match = re.match(r"(~=|==|!=|>=|<=|>|<)\s*(.+)", spec)
|
|
53
|
+
if not match:
|
|
54
|
+
# Bare version = equality
|
|
55
|
+
return installed_t == _parse_version(spec)
|
|
56
|
+
|
|
57
|
+
op, ver_str = match.group(1), match.group(2)
|
|
58
|
+
|
|
59
|
+
# Handle wildcard ==1.0.*
|
|
60
|
+
if ver_str.endswith(".*"):
|
|
61
|
+
prefix = _parse_version(ver_str[:-2])
|
|
62
|
+
if op == "==":
|
|
63
|
+
return installed_t[:len(prefix)] == prefix
|
|
64
|
+
elif op == "!=":
|
|
65
|
+
return installed_t[:len(prefix)] != prefix
|
|
66
|
+
|
|
67
|
+
ver_t = _parse_version(ver_str)
|
|
68
|
+
|
|
69
|
+
if op == "==":
|
|
70
|
+
return installed_t == ver_t
|
|
71
|
+
elif op == "!=":
|
|
72
|
+
return installed_t != ver_t
|
|
73
|
+
elif op == ">=":
|
|
74
|
+
return installed_t >= ver_t
|
|
75
|
+
elif op == "<=":
|
|
76
|
+
return installed_t <= ver_t
|
|
77
|
+
elif op == ">":
|
|
78
|
+
return installed_t > ver_t
|
|
79
|
+
elif op == "<":
|
|
80
|
+
return installed_t < ver_t
|
|
81
|
+
elif op == "~=":
|
|
82
|
+
# ~=1.4.2 means >=1.4.2,<1.5.0
|
|
83
|
+
return installed_t >= ver_t and installed_t[:len(ver_t) - 1] == ver_t[:len(ver_t) - 1]
|
|
84
|
+
|
|
85
|
+
return True
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
def _read_requires(dist_info_path: str) -> List[str]:
|
|
89
|
+
"""Read Requires-Dist from METADATA file."""
|
|
90
|
+
metadata_path = os.path.join(dist_info_path, "METADATA")
|
|
91
|
+
if not os.path.isfile(metadata_path):
|
|
92
|
+
return []
|
|
93
|
+
|
|
94
|
+
requires = []
|
|
95
|
+
try:
|
|
96
|
+
with open(metadata_path, "r", encoding="utf-8", errors="replace") as f:
|
|
97
|
+
for line in f:
|
|
98
|
+
line = line.strip()
|
|
99
|
+
if line.startswith("Requires-Dist:"):
|
|
100
|
+
req = line[len("Requires-Dist:"):].strip()
|
|
101
|
+
requires.append(req)
|
|
102
|
+
elif line == "" and requires:
|
|
103
|
+
# End of headers
|
|
104
|
+
break
|
|
105
|
+
except OSError:
|
|
106
|
+
pass
|
|
107
|
+
return requires
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def _parse_requirement(req_str: str) -> Optional[Tuple[str, str, str]]:
|
|
111
|
+
"""Parse a Requires-Dist string into (name, version_spec, extras/markers).
|
|
112
|
+
|
|
113
|
+
Returns None if the requirement has markers that exclude current env.
|
|
114
|
+
"""
|
|
115
|
+
# Strip inline comments
|
|
116
|
+
req_str = req_str.split("#")[0].strip()
|
|
117
|
+
|
|
118
|
+
# Handle markers (e.g., ; python_version >= "3.8")
|
|
119
|
+
marker = ""
|
|
120
|
+
if ";" in req_str:
|
|
121
|
+
req_str, marker = req_str.split(";", 1)
|
|
122
|
+
req_str = req_str.strip()
|
|
123
|
+
marker = marker.strip()
|
|
124
|
+
|
|
125
|
+
# Skip requirements with extra markers (optional deps)
|
|
126
|
+
if 'extra ==' in marker or 'extra ==' in marker:
|
|
127
|
+
return None
|
|
128
|
+
|
|
129
|
+
# Handle extras (e.g., package[extra])
|
|
130
|
+
extras = ""
|
|
131
|
+
if "[" in req_str:
|
|
132
|
+
base, rest = req_str.split("[", 1)
|
|
133
|
+
extras = rest.split("]")[0]
|
|
134
|
+
req_str = base + rest.split("]")[1] if "]" in rest else base
|
|
135
|
+
|
|
136
|
+
# Parse name and version spec
|
|
137
|
+
match = re.match(r"([A-Za-z0-9][\w.-]*)\s*(.*)", req_str.strip())
|
|
138
|
+
if not match:
|
|
139
|
+
return None
|
|
140
|
+
|
|
141
|
+
name = match.group(1).strip().lower().replace("-", "_").replace(".", "_")
|
|
142
|
+
version_spec = match.group(2).strip().strip("()")
|
|
143
|
+
|
|
144
|
+
return (name, version_spec, marker)
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
class DependencyConflictRule(Rule):
|
|
148
|
+
"""Detect version conflicts between installed packages.
|
|
149
|
+
|
|
150
|
+
Reads each package's Requires-Dist metadata and checks whether
|
|
151
|
+
the actually installed version satisfies the requirement. Catches
|
|
152
|
+
conflicts that pip missed, or that were introduced by manual installs.
|
|
153
|
+
"""
|
|
154
|
+
|
|
155
|
+
rule_id = "DEPENDENCY_VERSION_CONFLICT"
|
|
156
|
+
description = "Detect version conflicts between installed packages"
|
|
157
|
+
|
|
158
|
+
def evaluate(
|
|
159
|
+
self, profile: SystemProfile, packages: List[PackageBinaryInfo]
|
|
160
|
+
) -> List[Finding]:
|
|
161
|
+
findings: List[Finding] = []
|
|
162
|
+
|
|
163
|
+
# Build installed package index: normalized_name -> version
|
|
164
|
+
installed: Dict[str, str] = {}
|
|
165
|
+
dist_info_dirs: Dict[str, str] = {}
|
|
166
|
+
|
|
167
|
+
for pkg in packages:
|
|
168
|
+
norm_name = pkg.package_name.lower().replace("-", "_").replace(".", "_")
|
|
169
|
+
installed[norm_name] = pkg.package_version
|
|
170
|
+
dist_info_dirs[norm_name] = pkg.install_path
|
|
171
|
+
|
|
172
|
+
# Check each package's requirements
|
|
173
|
+
seen_conflicts: Set[str] = set()
|
|
174
|
+
|
|
175
|
+
for pkg in packages:
|
|
176
|
+
dist_info = pkg.install_path
|
|
177
|
+
if not dist_info or not os.path.isdir(dist_info):
|
|
178
|
+
continue
|
|
179
|
+
|
|
180
|
+
requires = _read_requires(dist_info)
|
|
181
|
+
|
|
182
|
+
for req_str in requires:
|
|
183
|
+
parsed = _parse_requirement(req_str)
|
|
184
|
+
if parsed is None:
|
|
185
|
+
continue
|
|
186
|
+
|
|
187
|
+
dep_name, version_spec, marker = parsed
|
|
188
|
+
|
|
189
|
+
if dep_name not in installed:
|
|
190
|
+
continue # Not installed — a different kind of issue
|
|
191
|
+
|
|
192
|
+
if not version_spec:
|
|
193
|
+
continue # No version constraint
|
|
194
|
+
|
|
195
|
+
actual_version = installed[dep_name]
|
|
196
|
+
|
|
197
|
+
if not _version_matches(actual_version, version_spec):
|
|
198
|
+
conflict_key = f"{pkg.package_name}->{dep_name}"
|
|
199
|
+
if conflict_key in seen_conflicts:
|
|
200
|
+
continue
|
|
201
|
+
seen_conflicts.add(conflict_key)
|
|
202
|
+
|
|
203
|
+
findings.append(Finding(
|
|
204
|
+
rule_id=self.rule_id,
|
|
205
|
+
severity=Severity.WARNING,
|
|
206
|
+
title=f"Version conflict: {pkg.package_name} requires "
|
|
207
|
+
f"{dep_name} {version_spec}",
|
|
208
|
+
explanation=(
|
|
209
|
+
f"{pkg.package_name}=={pkg.package_version} requires "
|
|
210
|
+
f"{dep_name}{version_spec} but "
|
|
211
|
+
f"{dep_name}=={actual_version} is installed. "
|
|
212
|
+
f"This version mismatch may cause ImportError or "
|
|
213
|
+
f"unexpected behavior at runtime."
|
|
214
|
+
),
|
|
215
|
+
package=pkg.package_name,
|
|
216
|
+
package_version=pkg.package_version,
|
|
217
|
+
suggestion=(
|
|
218
|
+
f"pip install '{dep_name}{version_spec}' "
|
|
219
|
+
f"or pip install --upgrade {pkg.package_name}"
|
|
220
|
+
),
|
|
221
|
+
confidence=0.95,
|
|
222
|
+
))
|
|
223
|
+
|
|
224
|
+
return findings
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
class MissingDependencyRule(Rule):
|
|
228
|
+
"""Detect required dependencies that are not installed at all."""
|
|
229
|
+
|
|
230
|
+
rule_id = "DEPENDENCY_MISSING"
|
|
231
|
+
description = "Detect required packages that are not installed"
|
|
232
|
+
|
|
233
|
+
def evaluate(
|
|
234
|
+
self, profile: SystemProfile, packages: List[PackageBinaryInfo]
|
|
235
|
+
) -> List[Finding]:
|
|
236
|
+
findings: List[Finding] = []
|
|
237
|
+
|
|
238
|
+
# Build installed index
|
|
239
|
+
installed: Set[str] = set()
|
|
240
|
+
for pkg in packages:
|
|
241
|
+
norm = pkg.package_name.lower().replace("-", "_").replace(".", "_")
|
|
242
|
+
installed.add(norm)
|
|
243
|
+
|
|
244
|
+
seen: Set[str] = set()
|
|
245
|
+
|
|
246
|
+
for pkg in packages:
|
|
247
|
+
dist_info = pkg.install_path
|
|
248
|
+
if not dist_info or not os.path.isdir(dist_info):
|
|
249
|
+
continue
|
|
250
|
+
|
|
251
|
+
requires = _read_requires(dist_info)
|
|
252
|
+
|
|
253
|
+
for req_str in requires:
|
|
254
|
+
parsed = _parse_requirement(req_str)
|
|
255
|
+
if parsed is None:
|
|
256
|
+
continue
|
|
257
|
+
|
|
258
|
+
dep_name, version_spec, marker = parsed
|
|
259
|
+
|
|
260
|
+
if dep_name not in installed and dep_name not in seen:
|
|
261
|
+
seen.add(dep_name)
|
|
262
|
+
findings.append(Finding(
|
|
263
|
+
rule_id=self.rule_id,
|
|
264
|
+
severity=Severity.WARNING,
|
|
265
|
+
title=f"Missing dependency: {pkg.package_name} requires "
|
|
266
|
+
f"{dep_name}",
|
|
267
|
+
explanation=(
|
|
268
|
+
f"{pkg.package_name}=={pkg.package_version} requires "
|
|
269
|
+
f"{dep_name} but it is not installed. This will cause "
|
|
270
|
+
f"an ImportError when {pkg.package_name} tries to use it."
|
|
271
|
+
),
|
|
272
|
+
package=pkg.package_name,
|
|
273
|
+
package_version=pkg.package_version,
|
|
274
|
+
suggestion=f"pip install {dep_name}",
|
|
275
|
+
confidence=0.9,
|
|
276
|
+
))
|
|
277
|
+
|
|
278
|
+
return findings
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
"""AI framework-specific compatibility rules.
|
|
2
|
+
|
|
3
|
+
Deep inspection rules for PyTorch, TensorFlow, TensorRT, and ONNX Runtime
|
|
4
|
+
that go beyond generic binary checks.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from typing import List
|
|
10
|
+
|
|
11
|
+
from pybinaryguard.models.enums import Severity
|
|
12
|
+
from pybinaryguard.models.finding import Finding
|
|
13
|
+
from pybinaryguard.models.package import PackageBinaryInfo
|
|
14
|
+
from pybinaryguard.models.system import SystemProfile
|
|
15
|
+
from pybinaryguard.rules.base import Rule
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class PYTORCH_CUDA_ABI_MISMATCH(Rule):
|
|
19
|
+
"""Detects PyTorch CUDA ABI mismatches using deep inspection."""
|
|
20
|
+
|
|
21
|
+
rule_id = "PYTORCH_CUDA_ABI_MISMATCH"
|
|
22
|
+
description = "Checks PyTorch CUDA ABI compatibility"
|
|
23
|
+
|
|
24
|
+
def is_applicable(self, profile: SystemProfile) -> bool:
|
|
25
|
+
return True # Checked per-package inside evaluate
|
|
26
|
+
|
|
27
|
+
def evaluate(
|
|
28
|
+
self, profile: SystemProfile, packages: List[PackageBinaryInfo]
|
|
29
|
+
) -> List[Finding]:
|
|
30
|
+
from pybinaryguard.frameworks.pytorch import check_pytorch_cuda_abi
|
|
31
|
+
|
|
32
|
+
findings: List[Finding] = []
|
|
33
|
+
for package in packages:
|
|
34
|
+
if package.package_name.lower() != "torch":
|
|
35
|
+
continue
|
|
36
|
+
|
|
37
|
+
issues = check_pytorch_cuda_abi(package, profile)
|
|
38
|
+
for issue in issues:
|
|
39
|
+
sev_map = {
|
|
40
|
+
"critical": Severity.CRITICAL,
|
|
41
|
+
"warning": Severity.WARNING,
|
|
42
|
+
"info": Severity.INFO,
|
|
43
|
+
}
|
|
44
|
+
severity = sev_map.get(
|
|
45
|
+
issue.get("severity", "warning"), Severity.WARNING
|
|
46
|
+
)
|
|
47
|
+
findings.append(
|
|
48
|
+
Finding(
|
|
49
|
+
rule_id=self.rule_id,
|
|
50
|
+
severity=severity,
|
|
51
|
+
title=f"PyTorch: {issue['issue'].replace('_', ' ').title()}",
|
|
52
|
+
explanation=issue["message"],
|
|
53
|
+
package=package.package_name,
|
|
54
|
+
suggestion=issue.get("recommendation", ""),
|
|
55
|
+
confidence=0.95,
|
|
56
|
+
)
|
|
57
|
+
)
|
|
58
|
+
return findings
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class PYTORCH_TORCHVISION_INCOMPATIBLE(Rule):
|
|
62
|
+
"""Detects incompatible PyTorch and torchvision versions."""
|
|
63
|
+
|
|
64
|
+
rule_id = "PYTORCH_TORCHVISION_INCOMPATIBLE"
|
|
65
|
+
description = "Checks PyTorch and torchvision version compatibility"
|
|
66
|
+
|
|
67
|
+
def is_applicable(self, profile: SystemProfile) -> bool:
|
|
68
|
+
return True
|
|
69
|
+
|
|
70
|
+
def evaluate(
|
|
71
|
+
self, profile: SystemProfile, packages: List[PackageBinaryInfo]
|
|
72
|
+
) -> List[Finding]:
|
|
73
|
+
from pybinaryguard.frameworks.pytorch import (
|
|
74
|
+
check_pytorch_torchvision_compatibility,
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
findings: List[Finding] = []
|
|
78
|
+
|
|
79
|
+
# Find torch and torchvision versions
|
|
80
|
+
torch_version = None
|
|
81
|
+
torchvision_pkg = None
|
|
82
|
+
for package in packages:
|
|
83
|
+
name_lower = package.package_name.lower()
|
|
84
|
+
if name_lower == "torch" and package.package_version:
|
|
85
|
+
torch_version = package.package_version
|
|
86
|
+
elif name_lower == "torchvision":
|
|
87
|
+
torchvision_pkg = package
|
|
88
|
+
|
|
89
|
+
if torch_version is None or torchvision_pkg is None:
|
|
90
|
+
return findings
|
|
91
|
+
|
|
92
|
+
error = check_pytorch_torchvision_compatibility(
|
|
93
|
+
torch_version, torchvision_pkg.package_version
|
|
94
|
+
)
|
|
95
|
+
if error:
|
|
96
|
+
findings.append(
|
|
97
|
+
Finding(
|
|
98
|
+
rule_id=self.rule_id,
|
|
99
|
+
severity=Severity.WARNING,
|
|
100
|
+
title="Incompatible PyTorch and torchvision versions",
|
|
101
|
+
explanation=error,
|
|
102
|
+
package=torchvision_pkg.package_name,
|
|
103
|
+
suggestion=(
|
|
104
|
+
"Install matching torchvision version for your "
|
|
105
|
+
"PyTorch version"
|
|
106
|
+
),
|
|
107
|
+
confidence=0.9,
|
|
108
|
+
)
|
|
109
|
+
)
|
|
110
|
+
return findings
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
class TENSORFLOW_COMPUTE_CAPABILITY_LOW(Rule):
|
|
114
|
+
"""Detects TensorFlow compute capability requirements not met."""
|
|
115
|
+
|
|
116
|
+
rule_id = "TENSORFLOW_COMPUTE_CAPABILITY_LOW"
|
|
117
|
+
description = "Checks GPU compute capability for TensorFlow"
|
|
118
|
+
|
|
119
|
+
def is_applicable(self, profile: SystemProfile) -> bool:
|
|
120
|
+
return True
|
|
121
|
+
|
|
122
|
+
def evaluate(
|
|
123
|
+
self, profile: SystemProfile, packages: List[PackageBinaryInfo]
|
|
124
|
+
) -> List[Finding]:
|
|
125
|
+
from pybinaryguard.frameworks.tensorflow import (
|
|
126
|
+
check_tensorflow_compute_capability,
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
findings: List[Finding] = []
|
|
130
|
+
for package in packages:
|
|
131
|
+
if not package.package_name.lower().startswith("tensorflow"):
|
|
132
|
+
continue
|
|
133
|
+
|
|
134
|
+
issues = check_tensorflow_compute_capability(package, profile)
|
|
135
|
+
for issue in issues:
|
|
136
|
+
sev_map = {
|
|
137
|
+
"critical": Severity.CRITICAL,
|
|
138
|
+
"warning": Severity.WARNING,
|
|
139
|
+
"info": Severity.INFO,
|
|
140
|
+
}
|
|
141
|
+
severity = sev_map.get(
|
|
142
|
+
issue.get("severity", "warning"), Severity.WARNING
|
|
143
|
+
)
|
|
144
|
+
findings.append(
|
|
145
|
+
Finding(
|
|
146
|
+
rule_id=self.rule_id,
|
|
147
|
+
severity=severity,
|
|
148
|
+
title=f"TensorFlow: {issue['issue'].replace('_', ' ').title()}",
|
|
149
|
+
explanation=issue["message"],
|
|
150
|
+
package=package.package_name,
|
|
151
|
+
suggestion=issue.get("recommendation", ""),
|
|
152
|
+
confidence=0.95,
|
|
153
|
+
)
|
|
154
|
+
)
|
|
155
|
+
return findings
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
class TENSORRT_INCOMPATIBLE(Rule):
|
|
159
|
+
"""Detects TensorRT compatibility issues."""
|
|
160
|
+
|
|
161
|
+
rule_id = "TENSORRT_INCOMPATIBLE"
|
|
162
|
+
description = "Checks TensorRT compatibility with CUDA/cuDNN"
|
|
163
|
+
|
|
164
|
+
def is_applicable(self, profile: SystemProfile) -> bool:
|
|
165
|
+
return True
|
|
166
|
+
|
|
167
|
+
def evaluate(
|
|
168
|
+
self, profile: SystemProfile, packages: List[PackageBinaryInfo]
|
|
169
|
+
) -> List[Finding]:
|
|
170
|
+
from pybinaryguard.frameworks.tensorrt import validate_tensorrt_engine
|
|
171
|
+
|
|
172
|
+
findings: List[Finding] = []
|
|
173
|
+
for package in packages:
|
|
174
|
+
if not package.package_name.lower().startswith("tensorrt"):
|
|
175
|
+
continue
|
|
176
|
+
|
|
177
|
+
issues = validate_tensorrt_engine(package, profile)
|
|
178
|
+
for issue in issues:
|
|
179
|
+
sev_map = {
|
|
180
|
+
"critical": Severity.CRITICAL,
|
|
181
|
+
"warning": Severity.WARNING,
|
|
182
|
+
"info": Severity.INFO,
|
|
183
|
+
}
|
|
184
|
+
severity = sev_map.get(
|
|
185
|
+
issue.get("severity", "warning"), Severity.WARNING
|
|
186
|
+
)
|
|
187
|
+
findings.append(
|
|
188
|
+
Finding(
|
|
189
|
+
rule_id=self.rule_id,
|
|
190
|
+
severity=severity,
|
|
191
|
+
title=f"TensorRT: {issue['issue'].replace('_', ' ').title()}",
|
|
192
|
+
explanation=issue["message"],
|
|
193
|
+
package=package.package_name,
|
|
194
|
+
suggestion=issue.get("recommendation", ""),
|
|
195
|
+
confidence=0.9,
|
|
196
|
+
)
|
|
197
|
+
)
|
|
198
|
+
return findings
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
class ONNX_RUNTIME_PROVIDER_MISMATCH(Rule):
|
|
202
|
+
"""Detects ONNX Runtime execution provider mismatches."""
|
|
203
|
+
|
|
204
|
+
rule_id = "ONNX_RUNTIME_PROVIDER_MISMATCH"
|
|
205
|
+
description = "Checks ONNX Runtime execution provider compatibility"
|
|
206
|
+
|
|
207
|
+
def is_applicable(self, profile: SystemProfile) -> bool:
|
|
208
|
+
return True
|
|
209
|
+
|
|
210
|
+
def evaluate(
|
|
211
|
+
self, profile: SystemProfile, packages: List[PackageBinaryInfo]
|
|
212
|
+
) -> List[Finding]:
|
|
213
|
+
from pybinaryguard.frameworks.onnxruntime import (
|
|
214
|
+
check_onnx_runtime_providers,
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
findings: List[Finding] = []
|
|
218
|
+
for package in packages:
|
|
219
|
+
if not package.package_name.lower().startswith("onnxruntime"):
|
|
220
|
+
continue
|
|
221
|
+
|
|
222
|
+
issues = check_onnx_runtime_providers(package, profile)
|
|
223
|
+
for issue in issues:
|
|
224
|
+
sev_map = {
|
|
225
|
+
"critical": Severity.CRITICAL,
|
|
226
|
+
"warning": Severity.WARNING,
|
|
227
|
+
"info": Severity.INFO,
|
|
228
|
+
}
|
|
229
|
+
severity = sev_map.get(
|
|
230
|
+
issue.get("severity", "warning"), Severity.WARNING
|
|
231
|
+
)
|
|
232
|
+
findings.append(
|
|
233
|
+
Finding(
|
|
234
|
+
rule_id=self.rule_id,
|
|
235
|
+
severity=severity,
|
|
236
|
+
title=f"ONNX Runtime: {issue['issue'].replace('_', ' ').title()}",
|
|
237
|
+
explanation=issue["message"],
|
|
238
|
+
package=package.package_name,
|
|
239
|
+
suggestion=issue.get("recommendation", ""),
|
|
240
|
+
confidence=0.85,
|
|
241
|
+
)
|
|
242
|
+
)
|
|
243
|
+
return findings
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
__all__ = [
|
|
247
|
+
"PYTORCH_CUDA_ABI_MISMATCH",
|
|
248
|
+
"PYTORCH_TORCHVISION_INCOMPATIBLE",
|
|
249
|
+
"TENSORFLOW_COMPUTE_CAPABILITY_LOW",
|
|
250
|
+
"TENSORRT_INCOMPATIBLE",
|
|
251
|
+
"ONNX_RUNTIME_PROVIDER_MISMATCH",
|
|
252
|
+
]
|