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.
Files changed (91) hide show
  1. pybinaryguard/__init__.py +78 -0
  2. pybinaryguard/__main__.py +7 -0
  3. pybinaryguard/_compat/__init__.py +0 -0
  4. pybinaryguard/agent/__init__.py +63 -0
  5. pybinaryguard/agent/guard.py +232 -0
  6. pybinaryguard/agent/recommender.py +283 -0
  7. pybinaryguard/agent/schema.py +200 -0
  8. pybinaryguard/agent/simulator.py +430 -0
  9. pybinaryguard/agent/tool_interface.py +474 -0
  10. pybinaryguard/analyzers/__init__.py +209 -0
  11. pybinaryguard/analyzers/base.py +40 -0
  12. pybinaryguard/analyzers/dependency_analyzer.py +336 -0
  13. pybinaryguard/analyzers/elf_analyzer.py +754 -0
  14. pybinaryguard/analyzers/symbol_analyzer.py +280 -0
  15. pybinaryguard/analyzers/wheel_analyzer.py +308 -0
  16. pybinaryguard/cli/__init__.py +5 -0
  17. pybinaryguard/cli/commands.py +414 -0
  18. pybinaryguard/cli/formatters.py +720 -0
  19. pybinaryguard/cli/main.py +250 -0
  20. pybinaryguard/diagnostics/__init__.py +13 -0
  21. pybinaryguard/diagnostics/explainer.py +356 -0
  22. pybinaryguard/diagnostics/findings.py +146 -0
  23. pybinaryguard/diagnostics/suggestions.py +508 -0
  24. pybinaryguard/frameworks/__init__.py +20 -0
  25. pybinaryguard/frameworks/onnxruntime.py +214 -0
  26. pybinaryguard/frameworks/pytorch.py +223 -0
  27. pybinaryguard/frameworks/tensorflow.py +266 -0
  28. pybinaryguard/frameworks/tensorrt.py +189 -0
  29. pybinaryguard/models/__init__.py +19 -0
  30. pybinaryguard/models/enums.py +102 -0
  31. pybinaryguard/models/finding.py +109 -0
  32. pybinaryguard/models/package.py +121 -0
  33. pybinaryguard/models/system.py +118 -0
  34. pybinaryguard/plugins/__init__.py +19 -0
  35. pybinaryguard/plugins/contrib/__init__.py +7 -0
  36. pybinaryguard/plugins/contrib/gstreamer.py +109 -0
  37. pybinaryguard/plugins/contrib/jetson.py +385 -0
  38. pybinaryguard/plugins/contrib/opencv.py +306 -0
  39. pybinaryguard/plugins/contrib/tensorrt.py +426 -0
  40. pybinaryguard/plugins/hooks.py +273 -0
  41. pybinaryguard/plugins/loader.py +190 -0
  42. pybinaryguard/predictor/__init__.py +23 -0
  43. pybinaryguard/predictor/dependency_graph.py +226 -0
  44. pybinaryguard/predictor/linker_simulator.py +161 -0
  45. pybinaryguard/predictor/predictor.py +144 -0
  46. pybinaryguard/predictor/resolver.py +282 -0
  47. pybinaryguard/probes/__init__.py +73 -0
  48. pybinaryguard/probes/base.py +45 -0
  49. pybinaryguard/probes/board_probe.py +248 -0
  50. pybinaryguard/probes/cpu_probe.py +176 -0
  51. pybinaryguard/probes/glibc_probe.py +215 -0
  52. pybinaryguard/probes/gpu_probe.py +430 -0
  53. pybinaryguard/probes/library_probe.py +132 -0
  54. pybinaryguard/probes/os_probe.py +227 -0
  55. pybinaryguard/probes/python_probe.py +135 -0
  56. pybinaryguard/probes/toolchain_probe.py +89 -0
  57. pybinaryguard/probes/venv_probe.py +111 -0
  58. pybinaryguard/profiles/__init__.py +12 -0
  59. pybinaryguard/profiles/engine.py +343 -0
  60. pybinaryguard/rules/__init__.py +24 -0
  61. pybinaryguard/rules/base.py +57 -0
  62. pybinaryguard/rules/builtin/__init__.py +136 -0
  63. pybinaryguard/rules/builtin/arch_rules.py +86 -0
  64. pybinaryguard/rules/builtin/board_profile_rules.py +282 -0
  65. pybinaryguard/rules/builtin/container_rules.py +170 -0
  66. pybinaryguard/rules/builtin/cpu_rules.py +204 -0
  67. pybinaryguard/rules/builtin/cuda_rules.py +760 -0
  68. pybinaryguard/rules/builtin/dependency_rules.py +278 -0
  69. pybinaryguard/rules/builtin/framework_rules.py +252 -0
  70. pybinaryguard/rules/builtin/glibc_rules.py +320 -0
  71. pybinaryguard/rules/builtin/numpy_rules.py +110 -0
  72. pybinaryguard/rules/builtin/predictive_rules.py +165 -0
  73. pybinaryguard/rules/builtin/python_abi_rules.py +259 -0
  74. pybinaryguard/rules/builtin/source_build_rules.py +150 -0
  75. pybinaryguard/rules/builtin/venv_rules.py +159 -0
  76. pybinaryguard/rules/engine.py +123 -0
  77. pybinaryguard/scanner.py +904 -0
  78. pybinaryguard/scoring/__init__.py +19 -0
  79. pybinaryguard/scoring/engine.py +416 -0
  80. pybinaryguard/snapshot/__init__.py +20 -0
  81. pybinaryguard/snapshot/generator.py +168 -0
  82. pybinaryguard/snapshot/lockfile.py +152 -0
  83. pybinaryguard/snapshot/verifier.py +315 -0
  84. pybinaryguard/validators/__init__.py +7 -0
  85. pybinaryguard/validators/import_validator.py +231 -0
  86. pybinaryguard-1.0.0.dist-info/METADATA +876 -0
  87. pybinaryguard-1.0.0.dist-info/RECORD +91 -0
  88. pybinaryguard-1.0.0.dist-info/WHEEL +5 -0
  89. pybinaryguard-1.0.0.dist-info/entry_points.txt +8 -0
  90. pybinaryguard-1.0.0.dist-info/licenses/LICENSE +21 -0
  91. pybinaryguard-1.0.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,152 @@
1
+ """Lockfile format for environment snapshots.
2
+
3
+ JSON-based lockfile that captures binary hashes, GPU stack, and system state
4
+ beyond what requirements.txt provides.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import json
10
+ from dataclasses import dataclass, field
11
+ from pathlib import Path
12
+ from typing import Any, Dict, List, Optional
13
+
14
+
15
+ @dataclass
16
+ class PackageSnapshot:
17
+ """Snapshot of a single package's binary state."""
18
+
19
+ name: str
20
+ version: str
21
+ cuda_version: Optional[str] = None
22
+ binary_hashes: Dict[str, str] = field(default_factory=dict) # path -> SHA256
23
+ manylinux_tag: Optional[str] = None
24
+ wheel_tags: List[str] = field(default_factory=list)
25
+
26
+
27
+ @dataclass
28
+ class SystemSnapshot:
29
+ """Snapshot of system configuration."""
30
+
31
+ python_version: str
32
+ platform: str
33
+ architecture: str
34
+ glibc_version: Optional[str] = None
35
+ cuda_runtime: Optional[str] = None
36
+ cuda_driver: Optional[str] = None
37
+ cuda_compute_capability: Optional[str] = None
38
+ tensorrt_version: Optional[str] = None
39
+ cudnn_version: Optional[str] = None
40
+ detected_board: Optional[str] = None
41
+ cpu_flags: List[str] = field(default_factory=list)
42
+ container_runtime: Optional[str] = None
43
+
44
+
45
+ @dataclass
46
+ class Lockfile:
47
+ """Complete environment lockfile.
48
+
49
+ Captures everything needed to reproduce a binary-compatible environment,
50
+ including information that pip freeze and conda export don't provide.
51
+ """
52
+
53
+ version: str = "1.0"
54
+ timestamp: str = ""
55
+ system: Optional[SystemSnapshot] = None
56
+ packages: List[PackageSnapshot] = field(default_factory=list)
57
+ metadata: Dict[str, Any] = field(default_factory=dict)
58
+
59
+ def to_dict(self) -> Dict[str, Any]:
60
+ """Convert lockfile to dictionary."""
61
+ return {
62
+ "pybinaryguard_lockfile": self.version,
63
+ "timestamp": self.timestamp,
64
+ "system": {
65
+ "python_version": self.system.python_version if self.system else "",
66
+ "platform": self.system.platform if self.system else "",
67
+ "architecture": self.system.architecture if self.system else "",
68
+ "glibc_version": self.system.glibc_version if self.system else None,
69
+ "cuda_runtime": self.system.cuda_runtime if self.system else None,
70
+ "cuda_driver": self.system.cuda_driver if self.system else None,
71
+ "cuda_compute_capability": self.system.cuda_compute_capability if self.system else None,
72
+ "tensorrt_version": self.system.tensorrt_version if self.system else None,
73
+ "cudnn_version": self.system.cudnn_version if self.system else None,
74
+ "detected_board": self.system.detected_board if self.system else None,
75
+ "cpu_flags": self.system.cpu_flags if self.system else [],
76
+ "container_runtime": self.system.container_runtime if self.system else None,
77
+ },
78
+ "packages": [
79
+ {
80
+ "name": pkg.name,
81
+ "version": pkg.version,
82
+ "cuda_version": pkg.cuda_version,
83
+ "binary_hashes": pkg.binary_hashes,
84
+ "manylinux_tag": pkg.manylinux_tag,
85
+ "wheel_tags": pkg.wheel_tags,
86
+ }
87
+ for pkg in self.packages
88
+ ],
89
+ "metadata": self.metadata,
90
+ }
91
+
92
+ def to_json(self, indent: int = 2) -> str:
93
+ """Convert lockfile to JSON string."""
94
+ return json.dumps(self.to_dict(), indent=indent, default=str)
95
+
96
+ def save(self, path: str) -> None:
97
+ """Save lockfile to disk."""
98
+ Path(path).write_text(self.to_json())
99
+
100
+ @classmethod
101
+ def from_dict(cls, data: Dict[str, Any]) -> Lockfile:
102
+ """Load lockfile from dictionary."""
103
+ system_data = data.get("system", {})
104
+ system = SystemSnapshot(
105
+ python_version=system_data.get("python_version", ""),
106
+ platform=system_data.get("platform", ""),
107
+ architecture=system_data.get("architecture", ""),
108
+ glibc_version=system_data.get("glibc_version"),
109
+ cuda_runtime=system_data.get("cuda_runtime"),
110
+ cuda_driver=system_data.get("cuda_driver"),
111
+ cuda_compute_capability=system_data.get("cuda_compute_capability"),
112
+ tensorrt_version=system_data.get("tensorrt_version"),
113
+ cudnn_version=system_data.get("cudnn_version"),
114
+ detected_board=system_data.get("detected_board"),
115
+ cpu_flags=system_data.get("cpu_flags", []),
116
+ container_runtime=system_data.get("container_runtime"),
117
+ )
118
+
119
+ packages = [
120
+ PackageSnapshot(
121
+ name=pkg["name"],
122
+ version=pkg["version"],
123
+ cuda_version=pkg.get("cuda_version"),
124
+ binary_hashes=pkg.get("binary_hashes", {}),
125
+ manylinux_tag=pkg.get("manylinux_tag"),
126
+ wheel_tags=pkg.get("wheel_tags", []),
127
+ )
128
+ for pkg in data.get("packages", [])
129
+ ]
130
+
131
+ return cls(
132
+ version=data.get("pybinaryguard_lockfile", "1.0"),
133
+ timestamp=data.get("timestamp", ""),
134
+ system=system,
135
+ packages=packages,
136
+ metadata=data.get("metadata", {}),
137
+ )
138
+
139
+ @classmethod
140
+ def from_json(cls, json_str: str) -> Lockfile:
141
+ """Load lockfile from JSON string."""
142
+ return cls.from_dict(json.loads(json_str))
143
+
144
+ @classmethod
145
+ def load(cls, path: str) -> Lockfile:
146
+ """Load lockfile from disk."""
147
+ return cls.from_json(Path(path).read_text())
148
+
149
+
150
+ def load_lockfile(path: str) -> Lockfile:
151
+ """Convenience function to load a lockfile."""
152
+ return Lockfile.load(path)
@@ -0,0 +1,315 @@
1
+ """Environment snapshot verifier.
2
+
3
+ Verifies current environment against a lockfile and runs compatibility checks.
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ import hashlib
9
+ from dataclasses import dataclass, field
10
+ from pathlib import Path
11
+ from typing import List, Optional
12
+
13
+ from pybinaryguard.models.system import SystemProfile
14
+ from pybinaryguard.models.package import PackageBinaryInfo
15
+ from pybinaryguard.snapshot.lockfile import Lockfile
16
+ from pybinaryguard.predictor import predict_import_failures
17
+
18
+
19
+ @dataclass
20
+ class VerificationIssue:
21
+ """An issue found during verification."""
22
+
23
+ severity: str # "error", "warning", "info"
24
+ category: str # "system", "package", "binary_hash", "compatibility"
25
+ message: str
26
+ package: Optional[str] = None
27
+ expected: Optional[str] = None
28
+ actual: Optional[str] = None
29
+
30
+
31
+ @dataclass
32
+ class VerificationResult:
33
+ """Result of environment verification."""
34
+
35
+ success: bool
36
+ issues: List[VerificationIssue] = field(default_factory=list)
37
+ packages_verified: int = 0
38
+ binaries_verified: int = 0
39
+ hash_mismatches: int = 0
40
+
41
+ @property
42
+ def error_count(self) -> int:
43
+ return sum(1 for i in self.issues if i.severity == "error")
44
+
45
+ @property
46
+ def warning_count(self) -> int:
47
+ return sum(1 for i in self.issues if i.severity == "warning")
48
+
49
+
50
+ class SnapshotVerifier:
51
+ """Verifies environment against a snapshot lockfile."""
52
+
53
+ def __init__(
54
+ self,
55
+ lockfile: Lockfile,
56
+ profile: SystemProfile,
57
+ packages: List[PackageBinaryInfo]
58
+ ):
59
+ """Initialize verifier.
60
+
61
+ Args:
62
+ lockfile: Lockfile to verify against
63
+ profile: Current system profile
64
+ packages: Currently installed packages
65
+ """
66
+ self.lockfile = lockfile
67
+ self.profile = profile
68
+ self.packages = packages
69
+
70
+ # Build package lookup
71
+ self.package_map = {pkg.name.lower(): pkg for pkg in packages}
72
+
73
+ def verify(
74
+ self,
75
+ check_hashes: bool = True,
76
+ run_compatibility_checks: bool = True
77
+ ) -> VerificationResult:
78
+ """Verify current environment against lockfile.
79
+
80
+ Args:
81
+ check_hashes: Whether to verify binary hashes
82
+ run_compatibility_checks: Whether to run predictive compatibility checks
83
+
84
+ Returns:
85
+ VerificationResult with issues found
86
+ """
87
+ result = VerificationResult(success=True)
88
+
89
+ # Verify system configuration
90
+ self._verify_system(result)
91
+
92
+ # Verify packages
93
+ self._verify_packages(result, check_hashes)
94
+
95
+ # Run compatibility checks
96
+ if run_compatibility_checks:
97
+ self._run_compatibility_checks(result)
98
+
99
+ # Determine overall success
100
+ result.success = result.error_count == 0
101
+
102
+ return result
103
+
104
+ def _verify_system(self, result: VerificationResult) -> None:
105
+ """Verify system configuration matches lockfile."""
106
+ if not self.lockfile.system:
107
+ return
108
+
109
+ snapshot_sys = self.lockfile.system
110
+
111
+ # Check Python version
112
+ py_ver = self.profile.python_version
113
+ current_py = f"{py_ver[0]}.{py_ver[1]}.{py_ver[2]}" if py_ver else ""
114
+
115
+ if snapshot_sys.python_version and current_py != snapshot_sys.python_version:
116
+ result.issues.append(
117
+ VerificationIssue(
118
+ severity="error",
119
+ category="system",
120
+ message="Python version mismatch",
121
+ expected=snapshot_sys.python_version,
122
+ actual=current_py,
123
+ )
124
+ )
125
+
126
+ # Check GLIBC version
127
+ if snapshot_sys.glibc_version and self.profile.glibc_version:
128
+ if self.profile.glibc_version != snapshot_sys.glibc_version:
129
+ result.issues.append(
130
+ VerificationIssue(
131
+ severity="warning",
132
+ category="system",
133
+ message="GLIBC version differs from snapshot",
134
+ expected=snapshot_sys.glibc_version,
135
+ actual=self.profile.glibc_version,
136
+ )
137
+ )
138
+
139
+ # Check CUDA runtime
140
+ if snapshot_sys.cuda_runtime and self.profile.cuda_runtime_version:
141
+ if self.profile.cuda_runtime_version != snapshot_sys.cuda_runtime:
142
+ result.issues.append(
143
+ VerificationIssue(
144
+ severity="warning",
145
+ category="system",
146
+ message="CUDA runtime version differs",
147
+ expected=snapshot_sys.cuda_runtime,
148
+ actual=self.profile.cuda_runtime_version,
149
+ )
150
+ )
151
+
152
+ # Check detected board
153
+ from pybinaryguard.profiles import match_board_profile
154
+ board_profile = match_board_profile(self.profile)
155
+ current_board = board_profile.display_name if board_profile else None
156
+
157
+ if snapshot_sys.detected_board and current_board != snapshot_sys.detected_board:
158
+ result.issues.append(
159
+ VerificationIssue(
160
+ severity="error",
161
+ category="system",
162
+ message="Board mismatch - environment was created for different hardware",
163
+ expected=snapshot_sys.detected_board,
164
+ actual=current_board or "Unknown",
165
+ )
166
+ )
167
+
168
+ def _verify_packages(self, result: VerificationResult, check_hashes: bool) -> None:
169
+ """Verify packages match lockfile."""
170
+ for pkg_snapshot in self.lockfile.packages:
171
+ result.packages_verified += 1
172
+
173
+ # Check if package is installed
174
+ current_pkg = self.package_map.get(pkg_snapshot.name.lower())
175
+
176
+ if not current_pkg:
177
+ result.issues.append(
178
+ VerificationIssue(
179
+ severity="error",
180
+ category="package",
181
+ message=f"Package missing from environment",
182
+ package=pkg_snapshot.name,
183
+ expected=pkg_snapshot.version,
184
+ actual=None,
185
+ )
186
+ )
187
+ continue
188
+
189
+ # Check version
190
+ if current_pkg.version != pkg_snapshot.version:
191
+ result.issues.append(
192
+ VerificationIssue(
193
+ severity="error",
194
+ category="package",
195
+ message=f"Package version mismatch",
196
+ package=pkg_snapshot.name,
197
+ expected=pkg_snapshot.version,
198
+ actual=current_pkg.version,
199
+ )
200
+ )
201
+
202
+ # Check CUDA version for GPU packages
203
+ if pkg_snapshot.cuda_version and current_pkg.cuda_version:
204
+ if current_pkg.cuda_version != pkg_snapshot.cuda_version:
205
+ result.issues.append(
206
+ VerificationIssue(
207
+ severity="warning",
208
+ category="package",
209
+ message=f"CUDA build version differs",
210
+ package=pkg_snapshot.name,
211
+ expected=pkg_snapshot.cuda_version,
212
+ actual=current_pkg.cuda_version,
213
+ )
214
+ )
215
+
216
+ # Verify binary hashes
217
+ if check_hashes and pkg_snapshot.binary_hashes:
218
+ self._verify_hashes(result, pkg_snapshot, current_pkg)
219
+
220
+ def _verify_hashes(
221
+ self,
222
+ result: VerificationResult,
223
+ pkg_snapshot,
224
+ current_pkg: PackageBinaryInfo
225
+ ) -> None:
226
+ """Verify binary file hashes."""
227
+ for rel_path, expected_hash in pkg_snapshot.binary_hashes.items():
228
+ result.binaries_verified += 1
229
+
230
+ # Find matching shared object
231
+ full_path = None
232
+ for so in current_pkg.shared_objects:
233
+ if so.path and rel_path in so.path:
234
+ full_path = so.path
235
+ break
236
+
237
+ if not full_path or not Path(full_path).exists():
238
+ result.issues.append(
239
+ VerificationIssue(
240
+ severity="error",
241
+ category="binary_hash",
242
+ message=f"Binary file missing or moved",
243
+ package=current_pkg.name,
244
+ expected=rel_path,
245
+ )
246
+ )
247
+ result.hash_mismatches += 1
248
+ continue
249
+
250
+ # Compute current hash
251
+ current_hash = self._compute_sha256(full_path)
252
+
253
+ if current_hash != expected_hash:
254
+ result.issues.append(
255
+ VerificationIssue(
256
+ severity="error",
257
+ category="binary_hash",
258
+ message=f"Binary hash mismatch - file may be corrupted or tampered",
259
+ package=current_pkg.name,
260
+ expected=expected_hash[:16] + "...",
261
+ actual=current_hash[:16] + "...",
262
+ )
263
+ )
264
+ result.hash_mismatches += 1
265
+
266
+ def _run_compatibility_checks(self, result: VerificationResult) -> None:
267
+ """Run predictive compatibility checks."""
268
+ for pkg in self.packages:
269
+ if not pkg.has_binaries:
270
+ continue
271
+
272
+ # Run predictive failure checks
273
+ failures = predict_import_failures(pkg, self.profile)
274
+
275
+ for failure in failures:
276
+ result.issues.append(
277
+ VerificationIssue(
278
+ severity="warning",
279
+ category="compatibility",
280
+ message=failure.error_message,
281
+ package=pkg.name,
282
+ )
283
+ )
284
+
285
+ @staticmethod
286
+ def _compute_sha256(file_path: str) -> str:
287
+ """Compute SHA256 hash of a file."""
288
+ sha256 = hashlib.sha256()
289
+ with open(file_path, "rb") as f:
290
+ for chunk in iter(lambda: f.read(65536), b""):
291
+ sha256.update(chunk)
292
+ return sha256.hexdigest()
293
+
294
+
295
+ def verify_snapshot(
296
+ lockfile: Lockfile,
297
+ profile: SystemProfile,
298
+ packages: List[PackageBinaryInfo],
299
+ check_hashes: bool = True,
300
+ run_compatibility_checks: bool = True
301
+ ) -> VerificationResult:
302
+ """Convenience function to verify environment against lockfile.
303
+
304
+ Args:
305
+ lockfile: Lockfile to verify against
306
+ profile: Current system profile
307
+ packages: Currently installed packages
308
+ check_hashes: Whether to verify binary hashes
309
+ run_compatibility_checks: Whether to run compatibility checks
310
+
311
+ Returns:
312
+ VerificationResult
313
+ """
314
+ verifier = SnapshotVerifier(lockfile, profile, packages)
315
+ return verifier.verify(check_hashes, run_compatibility_checks)
@@ -0,0 +1,7 @@
1
+ """Validators — runtime validation beyond static analysis."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pybinaryguard.validators.import_validator import ImportValidator, ImportTestResult
6
+
7
+ __all__ = ["ImportValidator", "ImportTestResult"]