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,189 @@
|
|
|
1
|
+
"""TensorRT framework deep inspection.
|
|
2
|
+
|
|
3
|
+
Validates TensorRT engine metadata, CUDA compatibility, and
|
|
4
|
+
plugin availability beyond generic binary checks.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import re
|
|
10
|
+
from typing import Dict, List, Optional
|
|
11
|
+
|
|
12
|
+
from pybinaryguard.models.package import PackageBinaryInfo
|
|
13
|
+
from pybinaryguard.models.system import SystemProfile
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def validate_tensorrt_engine(
|
|
17
|
+
package: PackageBinaryInfo,
|
|
18
|
+
profile: SystemProfile
|
|
19
|
+
) -> List[Dict[str, str]]:
|
|
20
|
+
"""Validate TensorRT installation and compatibility.
|
|
21
|
+
|
|
22
|
+
Checks:
|
|
23
|
+
- TensorRT version compatibility with CUDA
|
|
24
|
+
- Required plugins availability
|
|
25
|
+
- Compute capability support
|
|
26
|
+
- cuDNN version requirements
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
package: PackageBinaryInfo for tensorrt package
|
|
30
|
+
profile: System profile
|
|
31
|
+
|
|
32
|
+
Returns:
|
|
33
|
+
List of compatibility issues found
|
|
34
|
+
"""
|
|
35
|
+
issues: List[Dict[str, str]] = []
|
|
36
|
+
|
|
37
|
+
if not package.name.lower().startswith("tensorrt"):
|
|
38
|
+
return issues
|
|
39
|
+
|
|
40
|
+
# Extract TensorRT version from package
|
|
41
|
+
trt_version = package.version
|
|
42
|
+
if not trt_version:
|
|
43
|
+
return issues
|
|
44
|
+
|
|
45
|
+
# TensorRT 8.6+ requires CUDA 11.x or 12.x
|
|
46
|
+
if trt_version.startswith("8.6") or trt_version.startswith("8.5"):
|
|
47
|
+
if profile.cuda_runtime_version:
|
|
48
|
+
cuda_major = int(profile.cuda_runtime_version.split(".")[0])
|
|
49
|
+
if cuda_major < 11:
|
|
50
|
+
issues.append({
|
|
51
|
+
"issue": "tensorrt_cuda_too_old",
|
|
52
|
+
"severity": "critical",
|
|
53
|
+
"message": (
|
|
54
|
+
f"TensorRT {trt_version} requires CUDA 11.x or 12.x, "
|
|
55
|
+
f"but system has CUDA {profile.cuda_runtime_version}"
|
|
56
|
+
),
|
|
57
|
+
"recommendation": "Upgrade CUDA runtime or use TensorRT 7.x",
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
# Check compute capability requirements
|
|
61
|
+
if profile.cuda_compute_capability:
|
|
62
|
+
compute_major = int(profile.cuda_compute_capability.split(".")[0])
|
|
63
|
+
|
|
64
|
+
# TensorRT 8.x requires compute capability >= 5.0
|
|
65
|
+
if trt_version.startswith("8."):
|
|
66
|
+
if compute_major < 5:
|
|
67
|
+
issues.append({
|
|
68
|
+
"issue": "tensorrt_compute_capability_too_low",
|
|
69
|
+
"severity": "critical",
|
|
70
|
+
"message": (
|
|
71
|
+
f"TensorRT 8.x requires compute capability >= 5.0, "
|
|
72
|
+
f"but GPU has {profile.cuda_compute_capability}"
|
|
73
|
+
),
|
|
74
|
+
"recommendation": "Upgrade GPU or use TensorRT 7.x",
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
# Check for common TensorRT plugins
|
|
78
|
+
available_plugins = detect_tensorrt_plugins(package)
|
|
79
|
+
|
|
80
|
+
if not available_plugins:
|
|
81
|
+
issues.append({
|
|
82
|
+
"issue": "no_tensorrt_plugins",
|
|
83
|
+
"severity": "info",
|
|
84
|
+
"message": "No TensorRT plugins detected in installation",
|
|
85
|
+
"recommendation": "Install tensorrt-plugins package if needed for custom layers",
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
return issues
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def detect_tensorrt_plugins(package: PackageBinaryInfo) -> List[str]:
|
|
92
|
+
"""Detect available TensorRT plugins from shared objects.
|
|
93
|
+
|
|
94
|
+
Args:
|
|
95
|
+
package: PackageBinaryInfo for tensorrt package
|
|
96
|
+
|
|
97
|
+
Returns:
|
|
98
|
+
List of detected plugin names
|
|
99
|
+
"""
|
|
100
|
+
plugins = []
|
|
101
|
+
|
|
102
|
+
for so in package.shared_objects:
|
|
103
|
+
if not so.path:
|
|
104
|
+
continue
|
|
105
|
+
|
|
106
|
+
# TensorRT plugins typically named libnvinfer_plugin.so
|
|
107
|
+
if "plugin" in so.path.lower():
|
|
108
|
+
plugins.append(so.path)
|
|
109
|
+
|
|
110
|
+
return plugins
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def check_tensorrt_cudnn_compatibility(
|
|
114
|
+
tensorrt_version: Optional[str],
|
|
115
|
+
cudnn_version: Optional[str]
|
|
116
|
+
) -> Optional[str]:
|
|
117
|
+
"""Check TensorRT and cuDNN version compatibility.
|
|
118
|
+
|
|
119
|
+
Args:
|
|
120
|
+
tensorrt_version: TensorRT version (e.g., "8.6.1")
|
|
121
|
+
cudnn_version: cuDNN version (e.g., "8.9.0")
|
|
122
|
+
|
|
123
|
+
Returns:
|
|
124
|
+
Error message if incompatible, None if compatible
|
|
125
|
+
"""
|
|
126
|
+
if not tensorrt_version or not cudnn_version:
|
|
127
|
+
return None
|
|
128
|
+
|
|
129
|
+
# Compatibility matrix (TensorRT -> cuDNN major version)
|
|
130
|
+
compatibility_matrix = {
|
|
131
|
+
"8.6": "8",
|
|
132
|
+
"8.5": "8",
|
|
133
|
+
"8.4": "8",
|
|
134
|
+
"8.2": "8",
|
|
135
|
+
"8.0": "8",
|
|
136
|
+
"7.2": "7",
|
|
137
|
+
"7.1": "7",
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
try:
|
|
141
|
+
trt_major_minor = ".".join(tensorrt_version.split(".")[:2])
|
|
142
|
+
cudnn_major = cudnn_version.split(".")[0]
|
|
143
|
+
|
|
144
|
+
expected_cudnn_major = compatibility_matrix.get(trt_major_minor)
|
|
145
|
+
if expected_cudnn_major and cudnn_major != expected_cudnn_major:
|
|
146
|
+
return (
|
|
147
|
+
f"TensorRT {trt_major_minor} expects cuDNN {expected_cudnn_major}.x, "
|
|
148
|
+
f"but found cuDNN {cudnn_version}"
|
|
149
|
+
)
|
|
150
|
+
except (ValueError, IndexError):
|
|
151
|
+
pass
|
|
152
|
+
|
|
153
|
+
return None
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def detect_tensorrt_precision_support(
|
|
157
|
+
package: PackageBinaryInfo,
|
|
158
|
+
profile: SystemProfile
|
|
159
|
+
) -> Dict[str, bool]:
|
|
160
|
+
"""Detect supported precision modes (FP32, FP16, INT8).
|
|
161
|
+
|
|
162
|
+
Args:
|
|
163
|
+
package: PackageBinaryInfo for tensorrt package
|
|
164
|
+
profile: System profile
|
|
165
|
+
|
|
166
|
+
Returns:
|
|
167
|
+
Dictionary with supported precision modes
|
|
168
|
+
"""
|
|
169
|
+
support = {
|
|
170
|
+
"fp32": True, # Always supported
|
|
171
|
+
"fp16": False,
|
|
172
|
+
"int8": False,
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
# FP16 requires compute capability >= 5.3
|
|
176
|
+
if profile.cuda_compute_capability:
|
|
177
|
+
try:
|
|
178
|
+
major, minor = profile.cuda_compute_capability.split(".")
|
|
179
|
+
compute_val = int(major) * 10 + int(minor)
|
|
180
|
+
|
|
181
|
+
if compute_val >= 53: # 5.3
|
|
182
|
+
support["fp16"] = True
|
|
183
|
+
|
|
184
|
+
if compute_val >= 61: # 6.1
|
|
185
|
+
support["int8"] = True
|
|
186
|
+
except (ValueError, IndexError):
|
|
187
|
+
pass
|
|
188
|
+
|
|
189
|
+
return support
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""Core data models for PyBinaryGuard."""
|
|
2
|
+
|
|
3
|
+
from .enums import Architecture, ContainerRuntime, ScanMode, Severity
|
|
4
|
+
from .finding import Finding, ScanReport
|
|
5
|
+
from .package import PackageBinaryInfo, SharedObjectInfo, WheelTag
|
|
6
|
+
from .system import SystemProfile
|
|
7
|
+
|
|
8
|
+
__all__ = [
|
|
9
|
+
"Architecture",
|
|
10
|
+
"ContainerRuntime",
|
|
11
|
+
"Finding",
|
|
12
|
+
"PackageBinaryInfo",
|
|
13
|
+
"ScanMode",
|
|
14
|
+
"ScanReport",
|
|
15
|
+
"Severity",
|
|
16
|
+
"SharedObjectInfo",
|
|
17
|
+
"SystemProfile",
|
|
18
|
+
"WheelTag",
|
|
19
|
+
]
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
"""Core enumerations for PyBinaryGuard."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from enum import Enum
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Severity(Enum):
|
|
9
|
+
"""Severity level for diagnostic findings."""
|
|
10
|
+
|
|
11
|
+
CRITICAL = "critical"
|
|
12
|
+
WARNING = "warning"
|
|
13
|
+
INFO = "info"
|
|
14
|
+
PASSED = "passed"
|
|
15
|
+
|
|
16
|
+
def __lt__(self, other: object) -> bool:
|
|
17
|
+
if not isinstance(other, Severity):
|
|
18
|
+
return NotImplemented
|
|
19
|
+
order = {Severity.CRITICAL: 0, Severity.WARNING: 1, Severity.INFO: 2, Severity.PASSED: 3}
|
|
20
|
+
return order[self] < order[other]
|
|
21
|
+
|
|
22
|
+
def __le__(self, other: object) -> bool:
|
|
23
|
+
if not isinstance(other, Severity):
|
|
24
|
+
return NotImplemented
|
|
25
|
+
return self == other or self < other
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class Architecture(Enum):
|
|
29
|
+
"""CPU architecture identifiers."""
|
|
30
|
+
|
|
31
|
+
X86_64 = "x86_64"
|
|
32
|
+
AARCH64 = "aarch64"
|
|
33
|
+
ARMV7L = "armv7l"
|
|
34
|
+
I686 = "i686"
|
|
35
|
+
PPC64LE = "ppc64le"
|
|
36
|
+
S390X = "s390x"
|
|
37
|
+
UNKNOWN = "unknown"
|
|
38
|
+
|
|
39
|
+
@classmethod
|
|
40
|
+
def from_machine(cls, machine: str) -> Architecture:
|
|
41
|
+
"""Convert platform.machine() output to Architecture enum."""
|
|
42
|
+
mapping = {
|
|
43
|
+
"x86_64": cls.X86_64,
|
|
44
|
+
"AMD64": cls.X86_64,
|
|
45
|
+
"aarch64": cls.AARCH64,
|
|
46
|
+
"arm64": cls.AARCH64,
|
|
47
|
+
"armv7l": cls.ARMV7L,
|
|
48
|
+
"armv6l": cls.ARMV7L,
|
|
49
|
+
"i686": cls.I686,
|
|
50
|
+
"i386": cls.I686,
|
|
51
|
+
"ppc64le": cls.PPC64LE,
|
|
52
|
+
"s390x": cls.S390X,
|
|
53
|
+
}
|
|
54
|
+
return mapping.get(machine, cls.UNKNOWN)
|
|
55
|
+
|
|
56
|
+
@property
|
|
57
|
+
def elf_machine(self) -> int:
|
|
58
|
+
"""Return the ELF e_machine constant for this architecture."""
|
|
59
|
+
mapping = {
|
|
60
|
+
Architecture.X86_64: 62, # EM_X86_64
|
|
61
|
+
Architecture.AARCH64: 183, # EM_AARCH64
|
|
62
|
+
Architecture.ARMV7L: 40, # EM_ARM
|
|
63
|
+
Architecture.I686: 3, # EM_386
|
|
64
|
+
Architecture.PPC64LE: 21, # EM_PPC64
|
|
65
|
+
Architecture.S390X: 22, # EM_S390
|
|
66
|
+
}
|
|
67
|
+
return mapping.get(self, 0)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
class ContainerRuntime(Enum):
|
|
71
|
+
"""Container runtime types."""
|
|
72
|
+
|
|
73
|
+
DOCKER = "docker"
|
|
74
|
+
PODMAN = "podman"
|
|
75
|
+
LXC = "lxc"
|
|
76
|
+
CONTAINERD = "containerd"
|
|
77
|
+
NONE = "none"
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class ScanMode(Enum):
|
|
81
|
+
"""Scan depth mode.
|
|
82
|
+
|
|
83
|
+
FAST:
|
|
84
|
+
Metadata-only scan. Reads WHEEL/METADATA files and checks tags
|
|
85
|
+
against the system profile. Skips ELF binary parsing entirely.
|
|
86
|
+
Target: < 1 second for 100 packages.
|
|
87
|
+
|
|
88
|
+
STANDARD:
|
|
89
|
+
Default mode. Parses ELF headers for architecture, GLIBC version
|
|
90
|
+
requirements, and DT_NEEDED libraries. Runs all built-in rules.
|
|
91
|
+
Target: < 3 seconds for 100 packages (x86).
|
|
92
|
+
|
|
93
|
+
DEEP:
|
|
94
|
+
Full analysis. Everything in STANDARD plus: SHA256 hash verification
|
|
95
|
+
of all shared objects, recursive DT_NEEDED dependency chain resolution,
|
|
96
|
+
symbol-level version checking, and predictive import failure simulation.
|
|
97
|
+
Target: < 10 seconds for 100 packages.
|
|
98
|
+
"""
|
|
99
|
+
|
|
100
|
+
FAST = "fast"
|
|
101
|
+
STANDARD = "standard"
|
|
102
|
+
DEEP = "deep"
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
"""Finding and report data models."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass, field
|
|
6
|
+
from typing import Optional, List, Dict
|
|
7
|
+
|
|
8
|
+
from .enums import Severity
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@dataclass
|
|
12
|
+
class Finding:
|
|
13
|
+
"""A single diagnostic result from the compatibility check."""
|
|
14
|
+
|
|
15
|
+
rule_id: str
|
|
16
|
+
severity: Severity
|
|
17
|
+
title: str
|
|
18
|
+
explanation: str
|
|
19
|
+
technical_detail: str = ""
|
|
20
|
+
suggestion: str = ""
|
|
21
|
+
package: Optional[str] = None
|
|
22
|
+
package_version: Optional[str] = None
|
|
23
|
+
confidence: float = 1.0
|
|
24
|
+
related_error: Optional[str] = None
|
|
25
|
+
|
|
26
|
+
def as_dict(self) -> Dict[str, object]:
|
|
27
|
+
"""Convert to a JSON-serializable dict."""
|
|
28
|
+
result: Dict[str, object] = {
|
|
29
|
+
"rule_id": self.rule_id,
|
|
30
|
+
"severity": self.severity.value,
|
|
31
|
+
"title": self.title,
|
|
32
|
+
"explanation": self.explanation,
|
|
33
|
+
}
|
|
34
|
+
if self.technical_detail:
|
|
35
|
+
result["technical_detail"] = self.technical_detail
|
|
36
|
+
if self.suggestion:
|
|
37
|
+
result["suggestion"] = self.suggestion
|
|
38
|
+
if self.package:
|
|
39
|
+
result["package"] = self.package
|
|
40
|
+
if self.package_version:
|
|
41
|
+
result["package_version"] = self.package_version
|
|
42
|
+
if self.confidence < 1.0:
|
|
43
|
+
result["confidence"] = self.confidence
|
|
44
|
+
if self.related_error:
|
|
45
|
+
result["related_error"] = self.related_error
|
|
46
|
+
return result
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@dataclass
|
|
50
|
+
class ScanReport:
|
|
51
|
+
"""Complete scan report containing all findings and metadata."""
|
|
52
|
+
|
|
53
|
+
findings: List[Finding] = field(default_factory=list)
|
|
54
|
+
packages_scanned: int = 0
|
|
55
|
+
total_packages: int = 0
|
|
56
|
+
scan_duration_ms: float = 0.0
|
|
57
|
+
detected_board: Optional[str] = None # Display name of detected embedded board
|
|
58
|
+
score_breakdown: Optional[object] = None # ScoreBreakdown from scoring.engine
|
|
59
|
+
|
|
60
|
+
@property
|
|
61
|
+
def critical_count(self) -> int:
|
|
62
|
+
return sum(1 for f in self.findings if f.severity == Severity.CRITICAL)
|
|
63
|
+
|
|
64
|
+
@property
|
|
65
|
+
def warning_count(self) -> int:
|
|
66
|
+
return sum(1 for f in self.findings if f.severity == Severity.WARNING)
|
|
67
|
+
|
|
68
|
+
@property
|
|
69
|
+
def info_count(self) -> int:
|
|
70
|
+
return sum(1 for f in self.findings if f.severity == Severity.INFO)
|
|
71
|
+
|
|
72
|
+
@property
|
|
73
|
+
def passed_count(self) -> int:
|
|
74
|
+
return sum(1 for f in self.findings if f.severity == Severity.PASSED)
|
|
75
|
+
|
|
76
|
+
@property
|
|
77
|
+
def health_score(self) -> int:
|
|
78
|
+
"""Return the v2 weighted health score if available, else v1 linear."""
|
|
79
|
+
if self.score_breakdown is not None:
|
|
80
|
+
return round(self.score_breakdown.overall_score)
|
|
81
|
+
score = 100 - (self.critical_count * 25) - (self.warning_count * 5) - (self.info_count * 1)
|
|
82
|
+
return max(0, min(100, score))
|
|
83
|
+
|
|
84
|
+
@property
|
|
85
|
+
def health_label(self) -> str:
|
|
86
|
+
if self.score_breakdown is not None:
|
|
87
|
+
return self.score_breakdown.overall_label
|
|
88
|
+
score = self.health_score
|
|
89
|
+
if score >= 90:
|
|
90
|
+
return "Excellent"
|
|
91
|
+
elif score >= 70:
|
|
92
|
+
return "Good"
|
|
93
|
+
elif score >= 50:
|
|
94
|
+
return "Needs Attention"
|
|
95
|
+
else:
|
|
96
|
+
return "Critical"
|
|
97
|
+
|
|
98
|
+
def summary_dict(self) -> Dict[str, object]:
|
|
99
|
+
result: Dict[str, object] = {
|
|
100
|
+
"total_packages": self.total_packages,
|
|
101
|
+
"packages_scanned": self.packages_scanned,
|
|
102
|
+
"critical": self.critical_count,
|
|
103
|
+
"warning": self.warning_count,
|
|
104
|
+
"info": self.info_count,
|
|
105
|
+
"passed": self.passed_count,
|
|
106
|
+
}
|
|
107
|
+
if self.detected_board:
|
|
108
|
+
result["detected_board"] = self.detected_board
|
|
109
|
+
return result
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
"""Package binary information data models."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass, field
|
|
6
|
+
from typing import Optional, Tuple, List, Set
|
|
7
|
+
|
|
8
|
+
from .enums import Architecture
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@dataclass
|
|
12
|
+
class SharedObjectInfo:
|
|
13
|
+
"""Information extracted from a single .so file."""
|
|
14
|
+
|
|
15
|
+
path: str
|
|
16
|
+
filename: str
|
|
17
|
+
architecture: Architecture = Architecture.UNKNOWN
|
|
18
|
+
elf_class: int = 0 # 32 or 64
|
|
19
|
+
endianness: str = "little"
|
|
20
|
+
dt_needed: List[str] = field(default_factory=list)
|
|
21
|
+
dt_soname: Optional[str] = None
|
|
22
|
+
dt_rpath: Optional[str] = None
|
|
23
|
+
dt_runpath: Optional[str] = None
|
|
24
|
+
required_glibc: Optional[Tuple[int, int]] = None
|
|
25
|
+
required_glibcxx: Optional[str] = None
|
|
26
|
+
gnu_version_requirements: List[str] = field(default_factory=list)
|
|
27
|
+
has_python_symbols: bool = False
|
|
28
|
+
build_id: Optional[str] = None
|
|
29
|
+
file_size: int = 0
|
|
30
|
+
sha256: Optional[str] = None # Populated in DEEP scan mode
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@dataclass
|
|
34
|
+
class WheelTag:
|
|
35
|
+
"""A parsed wheel compatibility tag."""
|
|
36
|
+
|
|
37
|
+
interpreter: str # e.g., "cp312"
|
|
38
|
+
abi: str # e.g., "cp312"
|
|
39
|
+
platform: str # e.g., "manylinux_2_17_x86_64"
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@dataclass
|
|
43
|
+
class PackageBinaryInfo:
|
|
44
|
+
"""Complete binary analysis of an installed Python package."""
|
|
45
|
+
|
|
46
|
+
package_name: str
|
|
47
|
+
package_version: str
|
|
48
|
+
install_path: str
|
|
49
|
+
|
|
50
|
+
# Wheel metadata
|
|
51
|
+
wheel_tags: List[WheelTag] = field(default_factory=list)
|
|
52
|
+
is_pure_python: bool = True
|
|
53
|
+
|
|
54
|
+
# Binary analysis
|
|
55
|
+
shared_objects: List[SharedObjectInfo] = field(default_factory=list)
|
|
56
|
+
required_glibc: Optional[Tuple[int, int]] = None
|
|
57
|
+
required_glibcxx: Optional[str] = None
|
|
58
|
+
target_architecture: Optional[Architecture] = None
|
|
59
|
+
required_libraries: Set[str] = field(default_factory=set)
|
|
60
|
+
missing_libraries: Set[str] = field(default_factory=set)
|
|
61
|
+
|
|
62
|
+
# Framework-specific
|
|
63
|
+
cuda_build_version: Optional[Tuple[int, int]] = None
|
|
64
|
+
numpy_api_version: Optional[int] = None
|
|
65
|
+
|
|
66
|
+
@property
|
|
67
|
+
def name(self) -> str:
|
|
68
|
+
"""Alias for package_name (used by snapshot module)."""
|
|
69
|
+
return self.package_name
|
|
70
|
+
|
|
71
|
+
@property
|
|
72
|
+
def version(self) -> Optional[str]:
|
|
73
|
+
"""Alias for package_version (used by snapshot module)."""
|
|
74
|
+
return self.package_version
|
|
75
|
+
|
|
76
|
+
@property
|
|
77
|
+
def cuda_version(self) -> Optional[str]:
|
|
78
|
+
"""CUDA version string extracted from cuda_build_version tuple."""
|
|
79
|
+
if self.cuda_build_version:
|
|
80
|
+
return f"{self.cuda_build_version[0]}.{self.cuda_build_version[1]}"
|
|
81
|
+
return None
|
|
82
|
+
|
|
83
|
+
@property
|
|
84
|
+
def has_binaries(self) -> bool:
|
|
85
|
+
"""Whether this package contains any compiled extensions."""
|
|
86
|
+
return len(self.shared_objects) > 0
|
|
87
|
+
|
|
88
|
+
@property
|
|
89
|
+
def so_count(self) -> int:
|
|
90
|
+
"""Number of shared object files in this package."""
|
|
91
|
+
return len(self.shared_objects)
|
|
92
|
+
|
|
93
|
+
@property
|
|
94
|
+
def manylinux_tag(self) -> Optional[str]:
|
|
95
|
+
"""Extract the manylinux tag from wheel tags, if any."""
|
|
96
|
+
for tag in self.wheel_tags:
|
|
97
|
+
if "manylinux" in tag.platform:
|
|
98
|
+
return tag.platform
|
|
99
|
+
return None
|
|
100
|
+
|
|
101
|
+
@property
|
|
102
|
+
def manylinux_glibc(self) -> Optional[Tuple[int, int]]:
|
|
103
|
+
"""Extract the GLIBC version claimed by the manylinux tag."""
|
|
104
|
+
tag = self.manylinux_tag
|
|
105
|
+
if not tag:
|
|
106
|
+
return None
|
|
107
|
+
# Parse manylinux_2_17_x86_64 or manylinux2014_x86_64
|
|
108
|
+
import re
|
|
109
|
+
match = re.search(r"manylinux_(\d+)_(\d+)", tag)
|
|
110
|
+
if match:
|
|
111
|
+
return (int(match.group(1)), int(match.group(2)))
|
|
112
|
+
# Legacy tags
|
|
113
|
+
legacy_map = {
|
|
114
|
+
"manylinux1": (2, 5),
|
|
115
|
+
"manylinux2010": (2, 12),
|
|
116
|
+
"manylinux2014": (2, 17),
|
|
117
|
+
}
|
|
118
|
+
for prefix, version in legacy_map.items():
|
|
119
|
+
if tag.startswith(prefix):
|
|
120
|
+
return version
|
|
121
|
+
return None
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
"""System profile data model — the fingerprint of your machine."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass, field
|
|
6
|
+
from typing import Optional, Tuple, List, FrozenSet, Dict
|
|
7
|
+
|
|
8
|
+
from .enums import Architecture, ContainerRuntime
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@dataclass(frozen=True)
|
|
12
|
+
class SystemProfile:
|
|
13
|
+
"""Complete profile of the current system's binary compatibility surface."""
|
|
14
|
+
|
|
15
|
+
# Python
|
|
16
|
+
python_version: Tuple[int, int, int] = (0, 0, 0)
|
|
17
|
+
python_abi_tag: str = ""
|
|
18
|
+
python_implementation: str = "cpython"
|
|
19
|
+
python_executable: str = ""
|
|
20
|
+
stable_abi_supported: bool = False
|
|
21
|
+
python_debug_build: bool = False
|
|
22
|
+
|
|
23
|
+
# System
|
|
24
|
+
os_name: str = ""
|
|
25
|
+
os_version: str = ""
|
|
26
|
+
kernel_version: str = ""
|
|
27
|
+
architecture: Architecture = Architecture.UNKNOWN
|
|
28
|
+
glibc_version: Optional[Tuple[int, int]] = None
|
|
29
|
+
musl_version: Optional[Tuple[int, int]] = None
|
|
30
|
+
|
|
31
|
+
# CPU
|
|
32
|
+
cpu_model: str = ""
|
|
33
|
+
cpu_flags: FrozenSet[str] = field(default_factory=frozenset)
|
|
34
|
+
has_avx: bool = False
|
|
35
|
+
has_avx2: bool = False
|
|
36
|
+
has_avx512: bool = False
|
|
37
|
+
has_sse42: bool = False
|
|
38
|
+
has_neon: bool = False
|
|
39
|
+
cpu_cores: int = 0
|
|
40
|
+
|
|
41
|
+
# GPU
|
|
42
|
+
gpu_available: bool = False
|
|
43
|
+
gpu_driver_version: Optional[str] = None
|
|
44
|
+
cuda_runtime_version: Optional[Tuple[int, int]] = None
|
|
45
|
+
cuda_toolkit_version: Optional[Tuple[int, int]] = None
|
|
46
|
+
gpu_compute_capability: Optional[Tuple[int, int]] = None
|
|
47
|
+
gpu_name: Optional[str] = None
|
|
48
|
+
gpu_memory_mb: Optional[int] = None
|
|
49
|
+
cudnn_version: Optional[Tuple[int, int, int]] = None
|
|
50
|
+
|
|
51
|
+
# Environment
|
|
52
|
+
is_container: bool = False
|
|
53
|
+
container_runtime: ContainerRuntime = ContainerRuntime.NONE
|
|
54
|
+
is_virtual_machine: bool = False
|
|
55
|
+
is_embedded_board: bool = False
|
|
56
|
+
board_name: Optional[str] = None
|
|
57
|
+
jetpack_version: Optional[str] = None
|
|
58
|
+
|
|
59
|
+
# Build toolchain
|
|
60
|
+
toolchain_versions: Dict[str, str] = field(default_factory=dict)
|
|
61
|
+
default_cc: str = ""
|
|
62
|
+
default_cxx: str = ""
|
|
63
|
+
has_build_tools: bool = False
|
|
64
|
+
has_python_dev_headers: bool = False
|
|
65
|
+
|
|
66
|
+
# Virtual environment
|
|
67
|
+
venv_type: str = "system"
|
|
68
|
+
is_system_python: bool = True
|
|
69
|
+
is_virtual_env: bool = False
|
|
70
|
+
base_prefix: str = ""
|
|
71
|
+
prefix: str = ""
|
|
72
|
+
conda_env_name: str = ""
|
|
73
|
+
conda_prefix: str = ""
|
|
74
|
+
pip_user_site_enabled: bool = False
|
|
75
|
+
mixed_env_risk: bool = False
|
|
76
|
+
|
|
77
|
+
# Library paths
|
|
78
|
+
ld_library_path: Tuple[str, ...] = ()
|
|
79
|
+
ldconfig_cache: Dict[str, str] = field(default_factory=dict)
|
|
80
|
+
site_packages_paths: Tuple[str, ...] = ()
|
|
81
|
+
|
|
82
|
+
def summary(self) -> Dict[str, str]:
|
|
83
|
+
"""Return a human-readable summary dict for display."""
|
|
84
|
+
info: Dict[str, str] = {}
|
|
85
|
+
if self.python_version != (0, 0, 0):
|
|
86
|
+
py_ver = ".".join(str(v) for v in self.python_version)
|
|
87
|
+
info["Python"] = f"{py_ver} ({self.python_abi_tag}) @ {self.python_executable}"
|
|
88
|
+
if self.os_name:
|
|
89
|
+
info["OS"] = f"{self.os_name} {self.os_version} (kernel {self.kernel_version})"
|
|
90
|
+
if self.architecture != Architecture.UNKNOWN:
|
|
91
|
+
info["Architecture"] = self.architecture.value
|
|
92
|
+
if self.glibc_version:
|
|
93
|
+
info["GLIBC"] = f"{self.glibc_version[0]}.{self.glibc_version[1]}"
|
|
94
|
+
elif self.musl_version:
|
|
95
|
+
info["musl"] = f"{self.musl_version[0]}.{self.musl_version[1]}"
|
|
96
|
+
if self.cuda_runtime_version:
|
|
97
|
+
info["CUDA Runtime"] = f"{self.cuda_runtime_version[0]}.{self.cuda_runtime_version[1]}"
|
|
98
|
+
if self.gpu_driver_version:
|
|
99
|
+
info["GPU Driver"] = self.gpu_driver_version
|
|
100
|
+
if self.gpu_name:
|
|
101
|
+
cc = ""
|
|
102
|
+
if self.gpu_compute_capability:
|
|
103
|
+
cc = f" (compute {self.gpu_compute_capability[0]}.{self.gpu_compute_capability[1]})"
|
|
104
|
+
info["GPU"] = f"{self.gpu_name}{cc}"
|
|
105
|
+
if self.is_embedded_board and self.board_name:
|
|
106
|
+
info["Board"] = self.board_name
|
|
107
|
+
if self.jetpack_version:
|
|
108
|
+
info["JetPack"] = self.jetpack_version
|
|
109
|
+
if self.is_container:
|
|
110
|
+
info["Container"] = self.container_runtime.value
|
|
111
|
+
if self.venv_type != "system":
|
|
112
|
+
info["Environment"] = self.venv_type
|
|
113
|
+
if self.toolchain_versions:
|
|
114
|
+
cc = self.toolchain_versions.get("gcc") or self.toolchain_versions.get("clang")
|
|
115
|
+
if cc:
|
|
116
|
+
compiler = "gcc" if "gcc" in self.toolchain_versions else "clang"
|
|
117
|
+
info["Compiler"] = f"{compiler} {cc}"
|
|
118
|
+
return info
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""Plugin subsystem for PyBinaryGuard.
|
|
2
|
+
|
|
3
|
+
The plugin system allows third-party packages (and built-in contrib
|
|
4
|
+
modules) to extend PyBinaryGuard with additional probes, rules, board
|
|
5
|
+
detectors, framework checkers, reporters, and lifecycle hooks.
|
|
6
|
+
|
|
7
|
+
Public API
|
|
8
|
+
----------
|
|
9
|
+
.. autoclass:: HookRegistry
|
|
10
|
+
.. autofunction:: discover_plugins
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from pybinaryguard.plugins.hooks import HookRegistry
|
|
14
|
+
from pybinaryguard.plugins.loader import discover_plugins
|
|
15
|
+
|
|
16
|
+
__all__ = [
|
|
17
|
+
"HookRegistry",
|
|
18
|
+
"discover_plugins",
|
|
19
|
+
]
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
"""Built-in contrib plugins for PyBinaryGuard.
|
|
2
|
+
|
|
3
|
+
Each module in this package provides extensions for a specific platform
|
|
4
|
+
or framework. Contrib plugins are loaded automatically by
|
|
5
|
+
:func:`~pybinaryguard.plugins.loader.discover_plugins` and activate
|
|
6
|
+
only when the relevant hardware or software is detected on the system.
|
|
7
|
+
"""
|