oscura 0.10.0__py3-none-any.whl → 0.11.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.
- oscura/__init__.py +1 -1
- oscura/__main__.py +4 -0
- oscura/analyzers/ml/signal_classifier.py +6 -0
- oscura/analyzers/waveform/spectral.py +18 -11
- oscura/automotive/__init__.py +1 -1
- oscura/automotive/dtc/data.json +17 -102
- oscura/automotive/flexray/fibex.py +9 -1
- oscura/core/schemas/device_mapping.json +2 -8
- oscura/core/schemas/packet_format.json +4 -24
- oscura/core/schemas/protocol_definition.json +2 -12
- oscura/loaders/validation.py +17 -10
- oscura/sessions/legacy.py +49 -1
- oscura/workflows/batch/aggregate.py +5 -1
- oscura-0.11.0.dist-info/METADATA +460 -0
- {oscura-0.10.0.dist-info → oscura-0.11.0.dist-info}/RECORD +18 -18
- oscura-0.10.0.dist-info/METADATA +0 -641
- {oscura-0.10.0.dist-info → oscura-0.11.0.dist-info}/WHEEL +0 -0
- {oscura-0.10.0.dist-info → oscura-0.11.0.dist-info}/entry_points.txt +0 -0
- {oscura-0.10.0.dist-info → oscura-0.11.0.dist-info}/licenses/LICENSE +0 -0
oscura/__init__.py
CHANGED
oscura/__main__.py
CHANGED
|
@@ -106,6 +106,10 @@ def download_file(url: str, dest: Path, checksum: str | None = None) -> bool:
|
|
|
106
106
|
# Create SSL context that works in most environments
|
|
107
107
|
context = ssl.create_default_context()
|
|
108
108
|
|
|
109
|
+
# SEC-003: Validate URL scheme to prevent file:// attacks
|
|
110
|
+
if not url.startswith(("http://", "https://")):
|
|
111
|
+
raise ValueError(f"Unsupported URL scheme (only http/https allowed): {url}")
|
|
112
|
+
|
|
109
113
|
print(f" Downloading: {url}")
|
|
110
114
|
|
|
111
115
|
with urllib.request.urlopen(url, context=context, timeout=30) as response:
|
|
@@ -458,6 +458,12 @@ class MLSignalClassifier:
|
|
|
458
458
|
Restores the complete model state including the ML model, feature scaler,
|
|
459
459
|
feature names, and class labels.
|
|
460
460
|
|
|
461
|
+
Warning:
|
|
462
|
+
**Security**: Model files use pickle serialization. Only load model files
|
|
463
|
+
from trusted sources. Loading untrusted model files can execute arbitrary
|
|
464
|
+
code (CWE-502: Deserialization of Untrusted Data). Consider implementing
|
|
465
|
+
HMAC signature verification for production deployments.
|
|
466
|
+
|
|
461
467
|
Args:
|
|
462
468
|
path: Path to saved model file.
|
|
463
469
|
|
|
@@ -17,6 +17,7 @@ References:
|
|
|
17
17
|
|
|
18
18
|
from __future__ import annotations
|
|
19
19
|
|
|
20
|
+
import threading
|
|
20
21
|
from functools import lru_cache
|
|
21
22
|
from typing import TYPE_CHECKING, Any, Literal
|
|
22
23
|
|
|
@@ -32,8 +33,9 @@ if TYPE_CHECKING:
|
|
|
32
33
|
|
|
33
34
|
from oscura.core.types import MeasurementResult, WaveformTrace
|
|
34
35
|
|
|
35
|
-
# Global FFT cache statistics
|
|
36
|
+
# Global FFT cache statistics (thread-safe)
|
|
36
37
|
_fft_cache_stats = {"hits": 0, "misses": 0, "size": 128}
|
|
38
|
+
_fft_cache_lock = threading.Lock()
|
|
37
39
|
|
|
38
40
|
|
|
39
41
|
def get_fft_cache_stats() -> dict[str, int]:
|
|
@@ -46,7 +48,8 @@ def get_fft_cache_stats() -> dict[str, int]:
|
|
|
46
48
|
>>> stats = get_fft_cache_stats()
|
|
47
49
|
>>> print(f"Cache hit rate: {stats['hits'] / (stats['hits'] + stats['misses']):.1%}")
|
|
48
50
|
"""
|
|
49
|
-
|
|
51
|
+
with _fft_cache_lock:
|
|
52
|
+
return _fft_cache_stats.copy()
|
|
50
53
|
|
|
51
54
|
|
|
52
55
|
def clear_fft_cache() -> None:
|
|
@@ -58,8 +61,9 @@ def clear_fft_cache() -> None:
|
|
|
58
61
|
>>> clear_fft_cache() # Clear cached FFT results
|
|
59
62
|
"""
|
|
60
63
|
_compute_fft_cached.cache_clear()
|
|
61
|
-
|
|
62
|
-
|
|
64
|
+
with _fft_cache_lock:
|
|
65
|
+
_fft_cache_stats["hits"] = 0
|
|
66
|
+
_fft_cache_stats["misses"] = 0
|
|
63
67
|
|
|
64
68
|
|
|
65
69
|
def configure_fft_cache(size: int) -> None:
|
|
@@ -72,11 +76,12 @@ def configure_fft_cache(size: int) -> None:
|
|
|
72
76
|
>>> configure_fft_cache(256) # Increase cache size for better hit rate
|
|
73
77
|
"""
|
|
74
78
|
global _compute_fft_cached
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
79
|
+
with _fft_cache_lock:
|
|
80
|
+
_fft_cache_stats["size"] = size
|
|
81
|
+
# Recreate cache with new size
|
|
82
|
+
_compute_fft_cached = lru_cache(maxsize=size)(_compute_fft_impl)
|
|
83
|
+
_fft_cache_stats["hits"] = 0
|
|
84
|
+
_fft_cache_stats["misses"] = 0
|
|
80
85
|
|
|
81
86
|
|
|
82
87
|
def _compute_fft_impl(
|
|
@@ -270,7 +275,8 @@ def _fft_cached_path(
|
|
|
270
275
|
freq, magnitude_db, phase = _compute_fft_cached(
|
|
271
276
|
data_bytes, n, window, nfft_computed, detrend, sample_rate
|
|
272
277
|
)
|
|
273
|
-
|
|
278
|
+
with _fft_cache_lock:
|
|
279
|
+
_fft_cache_stats["hits"] += 1
|
|
274
280
|
|
|
275
281
|
if return_phase:
|
|
276
282
|
return freq, magnitude_db, phase
|
|
@@ -302,7 +308,8 @@ def _fft_direct_path(
|
|
|
302
308
|
Returns:
|
|
303
309
|
FFT results (with or without phase).
|
|
304
310
|
"""
|
|
305
|
-
|
|
311
|
+
with _fft_cache_lock:
|
|
312
|
+
_fft_cache_stats["misses"] += 1
|
|
306
313
|
|
|
307
314
|
w = get_window(window, n)
|
|
308
315
|
data_windowed = data_processed * w
|
oscura/automotive/__init__.py
CHANGED
oscura/automotive/dtc/data.json
CHANGED
|
@@ -266,12 +266,7 @@
|
|
|
266
266
|
"category": "Powertrain",
|
|
267
267
|
"severity": "High",
|
|
268
268
|
"system": "Throttle Control",
|
|
269
|
-
"possible_causes": [
|
|
270
|
-
"TPS circuit shorted to voltage",
|
|
271
|
-
"Faulty TPS sensor",
|
|
272
|
-
"Wiring harness open",
|
|
273
|
-
"ECM problem"
|
|
274
|
-
]
|
|
269
|
+
"possible_causes": ["TPS circuit shorted to voltage", "Faulty TPS sensor", "Wiring harness open", "ECM problem"]
|
|
275
270
|
},
|
|
276
271
|
"P0125": {
|
|
277
272
|
"code": "P0125",
|
|
@@ -865,12 +860,7 @@
|
|
|
865
860
|
"category": "Powertrain",
|
|
866
861
|
"severity": "Medium",
|
|
867
862
|
"system": "Emissions Control",
|
|
868
|
-
"possible_causes": [
|
|
869
|
-
"EGR valve stuck closed",
|
|
870
|
-
"EGR passages clogged",
|
|
871
|
-
"Faulty EGR valve",
|
|
872
|
-
"Vacuum leak"
|
|
873
|
-
]
|
|
863
|
+
"possible_causes": ["EGR valve stuck closed", "EGR passages clogged", "Faulty EGR valve", "Vacuum leak"]
|
|
874
864
|
},
|
|
875
865
|
"P0401": {
|
|
876
866
|
"code": "P0401",
|
|
@@ -891,12 +881,7 @@
|
|
|
891
881
|
"category": "Powertrain",
|
|
892
882
|
"severity": "Medium",
|
|
893
883
|
"system": "Emissions Control",
|
|
894
|
-
"possible_causes": [
|
|
895
|
-
"EGR valve stuck open",
|
|
896
|
-
"Faulty EGR valve",
|
|
897
|
-
"EGR vacuum solenoid fault",
|
|
898
|
-
"ECM problem"
|
|
899
|
-
]
|
|
884
|
+
"possible_causes": ["EGR valve stuck open", "Faulty EGR valve", "EGR vacuum solenoid fault", "ECM problem"]
|
|
900
885
|
},
|
|
901
886
|
"P0403": {
|
|
902
887
|
"code": "P0403",
|
|
@@ -958,12 +943,7 @@
|
|
|
958
943
|
"category": "Powertrain",
|
|
959
944
|
"severity": "Low",
|
|
960
945
|
"system": "Emissions Control",
|
|
961
|
-
"possible_causes": [
|
|
962
|
-
"Loose or missing fuel cap",
|
|
963
|
-
"EVAP system leak",
|
|
964
|
-
"Faulty purge valve",
|
|
965
|
-
"Faulty vent valve"
|
|
966
|
-
]
|
|
946
|
+
"possible_causes": ["Loose or missing fuel cap", "EVAP system leak", "Faulty purge valve", "Faulty vent valve"]
|
|
967
947
|
},
|
|
968
948
|
"P0441": {
|
|
969
949
|
"code": "P0441",
|
|
@@ -1075,12 +1055,7 @@
|
|
|
1075
1055
|
"category": "Powertrain",
|
|
1076
1056
|
"severity": "Low",
|
|
1077
1057
|
"system": "Idle Control",
|
|
1078
|
-
"possible_causes": [
|
|
1079
|
-
"Vacuum leak",
|
|
1080
|
-
"IAC valve fault",
|
|
1081
|
-
"Dirty throttle body",
|
|
1082
|
-
"PCV valve problem"
|
|
1083
|
-
]
|
|
1058
|
+
"possible_causes": ["Vacuum leak", "IAC valve fault", "Dirty throttle body", "PCV valve problem"]
|
|
1084
1059
|
},
|
|
1085
1060
|
"P0507": {
|
|
1086
1061
|
"code": "P0507",
|
|
@@ -1088,12 +1063,7 @@
|
|
|
1088
1063
|
"category": "Powertrain",
|
|
1089
1064
|
"severity": "Low",
|
|
1090
1065
|
"system": "Idle Control",
|
|
1091
|
-
"possible_causes": [
|
|
1092
|
-
"Vacuum leak",
|
|
1093
|
-
"IAC valve stuck open",
|
|
1094
|
-
"PCV valve stuck open",
|
|
1095
|
-
"EVAP purge valve leaking"
|
|
1096
|
-
]
|
|
1066
|
+
"possible_causes": ["Vacuum leak", "IAC valve stuck open", "PCV valve stuck open", "EVAP purge valve leaking"]
|
|
1097
1067
|
},
|
|
1098
1068
|
"P0600": {
|
|
1099
1069
|
"code": "P0600",
|
|
@@ -1127,12 +1097,7 @@
|
|
|
1127
1097
|
"category": "Powertrain",
|
|
1128
1098
|
"severity": "Critical",
|
|
1129
1099
|
"system": "Engine Control Module",
|
|
1130
|
-
"possible_causes": [
|
|
1131
|
-
"ECM not programmed",
|
|
1132
|
-
"ECM programming incomplete",
|
|
1133
|
-
"Wrong software version",
|
|
1134
|
-
"ECM fault"
|
|
1135
|
-
]
|
|
1100
|
+
"possible_causes": ["ECM not programmed", "ECM programming incomplete", "Wrong software version", "ECM fault"]
|
|
1136
1101
|
},
|
|
1137
1102
|
"P0603": {
|
|
1138
1103
|
"code": "P0603",
|
|
@@ -1205,12 +1170,7 @@
|
|
|
1205
1170
|
"category": "Powertrain",
|
|
1206
1171
|
"severity": "Medium",
|
|
1207
1172
|
"system": "Charging System",
|
|
1208
|
-
"possible_causes": [
|
|
1209
|
-
"Faulty alternator",
|
|
1210
|
-
"Wiring harness problem",
|
|
1211
|
-
"Poor electrical connection",
|
|
1212
|
-
"ECM fault"
|
|
1213
|
-
]
|
|
1173
|
+
"possible_causes": ["Faulty alternator", "Wiring harness problem", "Poor electrical connection", "ECM fault"]
|
|
1214
1174
|
},
|
|
1215
1175
|
"P0625": {
|
|
1216
1176
|
"code": "P0625",
|
|
@@ -1283,12 +1243,7 @@
|
|
|
1283
1243
|
"category": "Powertrain",
|
|
1284
1244
|
"severity": "High",
|
|
1285
1245
|
"system": "Transmission",
|
|
1286
|
-
"possible_causes": [
|
|
1287
|
-
"Faulty input speed sensor",
|
|
1288
|
-
"Wiring harness problem",
|
|
1289
|
-
"Sensor reluctor damaged",
|
|
1290
|
-
"TCM fault"
|
|
1291
|
-
]
|
|
1246
|
+
"possible_causes": ["Faulty input speed sensor", "Wiring harness problem", "Sensor reluctor damaged", "TCM fault"]
|
|
1292
1247
|
},
|
|
1293
1248
|
"P0720": {
|
|
1294
1249
|
"code": "P0720",
|
|
@@ -1491,12 +1446,7 @@
|
|
|
1491
1446
|
"category": "Chassis",
|
|
1492
1447
|
"severity": "High",
|
|
1493
1448
|
"system": "ABS",
|
|
1494
|
-
"possible_causes": [
|
|
1495
|
-
"Faulty valve relay",
|
|
1496
|
-
"Relay circuit problem",
|
|
1497
|
-
"ABS module fault",
|
|
1498
|
-
"Wiring harness issue"
|
|
1499
|
-
]
|
|
1449
|
+
"possible_causes": ["Faulty valve relay", "Relay circuit problem", "ABS module fault", "Wiring harness issue"]
|
|
1500
1450
|
},
|
|
1501
1451
|
"C0161": {
|
|
1502
1452
|
"code": "C0161",
|
|
@@ -2206,12 +2156,7 @@
|
|
|
2206
2156
|
"category": "Body",
|
|
2207
2157
|
"severity": "Low",
|
|
2208
2158
|
"system": "Lighting System",
|
|
2209
|
-
"possible_causes": [
|
|
2210
|
-
"Burned out bulb",
|
|
2211
|
-
"Wiring harness problem",
|
|
2212
|
-
"Lamp socket corrosion",
|
|
2213
|
-
"BCM fault"
|
|
2214
|
-
]
|
|
2159
|
+
"possible_causes": ["Burned out bulb", "Wiring harness problem", "Lamp socket corrosion", "BCM fault"]
|
|
2215
2160
|
},
|
|
2216
2161
|
"B0601": {
|
|
2217
2162
|
"code": "B0601",
|
|
@@ -2232,12 +2177,7 @@
|
|
|
2232
2177
|
"category": "Body",
|
|
2233
2178
|
"severity": "Low",
|
|
2234
2179
|
"system": "Lighting System",
|
|
2235
|
-
"possible_causes": [
|
|
2236
|
-
"Burned out turn signal bulb",
|
|
2237
|
-
"Wiring harness problem",
|
|
2238
|
-
"Flasher relay fault",
|
|
2239
|
-
"BCM fault"
|
|
2240
|
-
]
|
|
2180
|
+
"possible_causes": ["Burned out turn signal bulb", "Wiring harness problem", "Flasher relay fault", "BCM fault"]
|
|
2241
2181
|
},
|
|
2242
2182
|
"B0603": {
|
|
2243
2183
|
"code": "B0603",
|
|
@@ -2245,12 +2185,7 @@
|
|
|
2245
2185
|
"category": "Body",
|
|
2246
2186
|
"severity": "Low",
|
|
2247
2187
|
"system": "Lighting System",
|
|
2248
|
-
"possible_causes": [
|
|
2249
|
-
"Burned out turn signal bulb",
|
|
2250
|
-
"Wiring harness problem",
|
|
2251
|
-
"Flasher relay fault",
|
|
2252
|
-
"BCM fault"
|
|
2253
|
-
]
|
|
2188
|
+
"possible_causes": ["Burned out turn signal bulb", "Wiring harness problem", "Flasher relay fault", "BCM fault"]
|
|
2254
2189
|
},
|
|
2255
2190
|
"B0604": {
|
|
2256
2191
|
"code": "B0604",
|
|
@@ -2362,12 +2297,7 @@
|
|
|
2362
2297
|
"category": "Body",
|
|
2363
2298
|
"severity": "Low",
|
|
2364
2299
|
"system": "Keyless Entry",
|
|
2365
|
-
"possible_causes": [
|
|
2366
|
-
"Key fob battery weak",
|
|
2367
|
-
"Key fob not synchronized",
|
|
2368
|
-
"BCM fault",
|
|
2369
|
-
"Receiver antenna fault"
|
|
2370
|
-
]
|
|
2300
|
+
"possible_causes": ["Key fob battery weak", "Key fob not synchronized", "BCM fault", "Receiver antenna fault"]
|
|
2371
2301
|
},
|
|
2372
2302
|
"B1300": {
|
|
2373
2303
|
"code": "B1300",
|
|
@@ -2466,12 +2396,7 @@
|
|
|
2466
2396
|
"category": "Network",
|
|
2467
2397
|
"severity": "Critical",
|
|
2468
2398
|
"system": "CAN Bus",
|
|
2469
|
-
"possible_causes": [
|
|
2470
|
-
"TCM not powered",
|
|
2471
|
-
"CAN bus wiring problem",
|
|
2472
|
-
"TCM internal fault",
|
|
2473
|
-
"CAN bus short circuit"
|
|
2474
|
-
]
|
|
2399
|
+
"possible_causes": ["TCM not powered", "CAN bus wiring problem", "TCM internal fault", "CAN bus short circuit"]
|
|
2475
2400
|
},
|
|
2476
2401
|
"U0102": {
|
|
2477
2402
|
"code": "U0102",
|
|
@@ -2544,12 +2469,7 @@
|
|
|
2544
2469
|
"category": "Network",
|
|
2545
2470
|
"severity": "High",
|
|
2546
2471
|
"system": "CAN Bus",
|
|
2547
|
-
"possible_causes": [
|
|
2548
|
-
"BCM not powered",
|
|
2549
|
-
"CAN bus wiring problem",
|
|
2550
|
-
"BCM internal fault",
|
|
2551
|
-
"Ground connection issue"
|
|
2552
|
-
]
|
|
2472
|
+
"possible_causes": ["BCM not powered", "CAN bus wiring problem", "BCM internal fault", "Ground connection issue"]
|
|
2553
2473
|
},
|
|
2554
2474
|
"U0141": {
|
|
2555
2475
|
"code": "U0141",
|
|
@@ -2557,12 +2477,7 @@
|
|
|
2557
2477
|
"category": "Network",
|
|
2558
2478
|
"severity": "High",
|
|
2559
2479
|
"system": "CAN Bus",
|
|
2560
|
-
"possible_causes": [
|
|
2561
|
-
"BCM not powered",
|
|
2562
|
-
"CAN bus wiring problem",
|
|
2563
|
-
"Module internal fault",
|
|
2564
|
-
"Connector problem"
|
|
2565
|
-
]
|
|
2480
|
+
"possible_causes": ["BCM not powered", "CAN bus wiring problem", "Module internal fault", "Connector problem"]
|
|
2566
2481
|
},
|
|
2567
2482
|
"U0151": {
|
|
2568
2483
|
"code": "U0151",
|
|
@@ -299,7 +299,15 @@ class FIBEXImporter:
|
|
|
299
299
|
if not fibex_path.exists():
|
|
300
300
|
raise FileNotFoundError(f"FIBEX file not found: {fibex_path}")
|
|
301
301
|
|
|
302
|
-
|
|
302
|
+
# SEC-004: Protect against XXE attacks by disabling entity expansion
|
|
303
|
+
parser = ET.XMLParser()
|
|
304
|
+
try:
|
|
305
|
+
# Python < 3.12: entity attribute is writable
|
|
306
|
+
parser.entity = {} # type: ignore[misc]
|
|
307
|
+
except AttributeError:
|
|
308
|
+
# Python >= 3.12: entity attribute is read-only, default behavior is safe
|
|
309
|
+
pass
|
|
310
|
+
tree = ET.parse(fibex_path, parser=parser)
|
|
303
311
|
root = tree.getroot()
|
|
304
312
|
|
|
305
313
|
# Extract cluster configuration
|
|
@@ -149,20 +149,14 @@
|
|
|
149
149
|
"type": "array",
|
|
150
150
|
"description": "Device IDs to include (whitelist)",
|
|
151
151
|
"items": {
|
|
152
|
-
"oneOf": [
|
|
153
|
-
{ "type": "integer" },
|
|
154
|
-
{ "type": "string", "pattern": "^0[xX][0-9A-Fa-f]+$" }
|
|
155
|
-
]
|
|
152
|
+
"oneOf": [{ "type": "integer" }, { "type": "string", "pattern": "^0[xX][0-9A-Fa-f]+$" }]
|
|
156
153
|
}
|
|
157
154
|
},
|
|
158
155
|
"exclude_devices": {
|
|
159
156
|
"type": "array",
|
|
160
157
|
"description": "Device IDs to exclude (blacklist)",
|
|
161
158
|
"items": {
|
|
162
|
-
"oneOf": [
|
|
163
|
-
{ "type": "integer" },
|
|
164
|
-
{ "type": "string", "pattern": "^0[xX][0-9A-Fa-f]+$" }
|
|
165
|
-
]
|
|
159
|
+
"oneOf": [{ "type": "integer" }, { "type": "string", "pattern": "^0[xX][0-9A-Fa-f]+$" }]
|
|
166
160
|
}
|
|
167
161
|
},
|
|
168
162
|
"include_categories": {
|
|
@@ -118,10 +118,7 @@
|
|
|
118
118
|
},
|
|
119
119
|
"value": {
|
|
120
120
|
"description": "Expected constant value for validation",
|
|
121
|
-
"oneOf": [
|
|
122
|
-
{ "type": "integer" },
|
|
123
|
-
{ "type": "array", "items": { "type": "integer" } }
|
|
124
|
-
]
|
|
121
|
+
"oneOf": [{ "type": "integer" }, { "type": "array", "items": { "type": "integer" } }]
|
|
125
122
|
},
|
|
126
123
|
"description": {
|
|
127
124
|
"type": "string",
|
|
@@ -188,18 +185,7 @@
|
|
|
188
185
|
},
|
|
189
186
|
"type": {
|
|
190
187
|
"type": "string",
|
|
191
|
-
"enum": [
|
|
192
|
-
"uint8",
|
|
193
|
-
"uint16",
|
|
194
|
-
"uint32",
|
|
195
|
-
"uint64",
|
|
196
|
-
"int8",
|
|
197
|
-
"int16",
|
|
198
|
-
"int32",
|
|
199
|
-
"int64",
|
|
200
|
-
"float32",
|
|
201
|
-
"float64"
|
|
202
|
-
],
|
|
188
|
+
"enum": ["uint8", "uint16", "uint32", "uint64", "int8", "int16", "int32", "int64", "float32", "float64"],
|
|
203
189
|
"description": "Sample data type"
|
|
204
190
|
},
|
|
205
191
|
"endian": {
|
|
@@ -303,10 +289,7 @@
|
|
|
303
289
|
},
|
|
304
290
|
"expected": {
|
|
305
291
|
"description": "Expected value",
|
|
306
|
-
"oneOf": [
|
|
307
|
-
{ "type": "integer" },
|
|
308
|
-
{ "type": "array", "items": { "type": "integer" } }
|
|
309
|
-
]
|
|
292
|
+
"oneOf": [{ "type": "integer" }, { "type": "array", "items": { "type": "integer" } }]
|
|
310
293
|
},
|
|
311
294
|
"on_failure": {
|
|
312
295
|
"type": "string",
|
|
@@ -379,10 +362,7 @@
|
|
|
379
362
|
},
|
|
380
363
|
"pattern": {
|
|
381
364
|
"description": "Idle pattern to detect",
|
|
382
|
-
"oneOf": [
|
|
383
|
-
{ "type": "string", "enum": ["auto", "zeros", "ones"] },
|
|
384
|
-
{ "type": "integer" }
|
|
385
|
-
]
|
|
365
|
+
"oneOf": [{ "type": "string", "enum": ["auto", "zeros", "ones"] }, { "type": "integer" }]
|
|
386
366
|
},
|
|
387
367
|
"min_duration": {
|
|
388
368
|
"type": "integer",
|
|
@@ -241,12 +241,7 @@
|
|
|
241
241
|
},
|
|
242
242
|
"value": {
|
|
243
243
|
"description": "Expected constant value for validation",
|
|
244
|
-
"oneOf": [
|
|
245
|
-
{ "type": "integer" },
|
|
246
|
-
{ "type": "number" },
|
|
247
|
-
{ "type": "string" },
|
|
248
|
-
{ "type": "array" }
|
|
249
|
-
]
|
|
244
|
+
"oneOf": [{ "type": "integer" }, { "type": "number" }, { "type": "string" }, { "type": "array" }]
|
|
250
245
|
},
|
|
251
246
|
"condition": {
|
|
252
247
|
"type": "string",
|
|
@@ -331,12 +326,7 @@
|
|
|
331
326
|
},
|
|
332
327
|
"expected": {
|
|
333
328
|
"description": "Expected value",
|
|
334
|
-
"oneOf": [
|
|
335
|
-
{ "type": "integer" },
|
|
336
|
-
{ "type": "number" },
|
|
337
|
-
{ "type": "string" },
|
|
338
|
-
{ "type": "array" }
|
|
339
|
-
]
|
|
329
|
+
"oneOf": [{ "type": "integer" }, { "type": "number" }, { "type": "string" }, { "type": "array" }]
|
|
340
330
|
},
|
|
341
331
|
"on_mismatch": {
|
|
342
332
|
"type": "string",
|
oscura/loaders/validation.py
CHANGED
|
@@ -475,24 +475,31 @@ class PacketValidator:
|
|
|
475
475
|
|
|
476
476
|
@staticmethod
|
|
477
477
|
def _crc32(data: bytes, poly: int = 0xEDB88320) -> int:
|
|
478
|
-
"""Compute CRC-32 checksum.
|
|
478
|
+
"""Compute CRC-32 checksum using native implementation.
|
|
479
479
|
|
|
480
480
|
Args:
|
|
481
481
|
data: Data to checksum.
|
|
482
482
|
poly: CRC polynomial (default: 0xEDB88320 for CRC-32).
|
|
483
|
+
Note: Only standard CRC-32 polynomial is supported by native implementation.
|
|
483
484
|
|
|
484
485
|
Returns:
|
|
485
486
|
CRC-32 value.
|
|
487
|
+
|
|
488
|
+
Note:
|
|
489
|
+
Uses zlib.crc32() for performance (~100x faster than pure Python).
|
|
490
|
+
Custom polynomials are not supported - raises ValueError if non-standard poly provided.
|
|
486
491
|
"""
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
492
|
+
import zlib
|
|
493
|
+
|
|
494
|
+
# Verify standard CRC-32 polynomial (zlib only supports this)
|
|
495
|
+
if poly != 0xEDB88320:
|
|
496
|
+
raise ValueError(
|
|
497
|
+
f"Non-standard CRC polynomial {poly:#x} not supported by native implementation. "
|
|
498
|
+
"Only standard CRC-32 (0xEDB88320) is available."
|
|
499
|
+
)
|
|
500
|
+
|
|
501
|
+
# zlib.crc32 returns signed int on some platforms, mask to unsigned
|
|
502
|
+
return zlib.crc32(data) & 0xFFFFFFFF
|
|
496
503
|
|
|
497
504
|
def get_statistics(self) -> ValidationStats:
|
|
498
505
|
"""Get aggregate validation statistics.
|
oscura/sessions/legacy.py
CHANGED
|
@@ -17,6 +17,7 @@ import gzip
|
|
|
17
17
|
import hashlib
|
|
18
18
|
import hmac
|
|
19
19
|
import pickle
|
|
20
|
+
import secrets
|
|
20
21
|
from dataclasses import dataclass, field
|
|
21
22
|
from datetime import datetime
|
|
22
23
|
from enum import Enum
|
|
@@ -28,7 +29,45 @@ from oscura.core.exceptions import SecurityError
|
|
|
28
29
|
# Session file format constants
|
|
29
30
|
_SESSION_MAGIC = b"OSC1" # Magic bytes for new format with signature
|
|
30
31
|
_SESSION_SIGNATURE_SIZE = 32 # SHA256 hash size in bytes
|
|
31
|
-
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _get_security_key() -> bytes:
|
|
35
|
+
"""Get or generate per-installation session security key.
|
|
36
|
+
|
|
37
|
+
The key is generated once per installation and stored in ~/.oscura/session_key
|
|
38
|
+
with restrictive permissions (0o600). This provides better security than a
|
|
39
|
+
shared hardcoded key.
|
|
40
|
+
|
|
41
|
+
Returns:
|
|
42
|
+
32-byte security key for HMAC signing.
|
|
43
|
+
"""
|
|
44
|
+
key_file = Path.home() / ".oscura" / "session_key"
|
|
45
|
+
|
|
46
|
+
if key_file.exists():
|
|
47
|
+
# Load existing key
|
|
48
|
+
try:
|
|
49
|
+
return key_file.read_bytes()
|
|
50
|
+
except (OSError, PermissionError):
|
|
51
|
+
# Fall back to generating new key if can't read
|
|
52
|
+
pass
|
|
53
|
+
|
|
54
|
+
# Generate new random key
|
|
55
|
+
key_file.parent.mkdir(parents=True, exist_ok=True)
|
|
56
|
+
key = secrets.token_bytes(32)
|
|
57
|
+
|
|
58
|
+
# Write with restrictive permissions
|
|
59
|
+
try:
|
|
60
|
+
key_file.write_bytes(key)
|
|
61
|
+
key_file.chmod(0o600) # Owner read/write only
|
|
62
|
+
except (OSError, PermissionError):
|
|
63
|
+
# Can't write key file - continue with ephemeral key
|
|
64
|
+
# This happens in read-only filesystems or restricted environments
|
|
65
|
+
pass
|
|
66
|
+
|
|
67
|
+
return key
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
_SECURITY_KEY = _get_security_key()
|
|
32
71
|
|
|
33
72
|
|
|
34
73
|
class AnnotationType(Enum):
|
|
@@ -709,6 +748,15 @@ class Session:
|
|
|
709
748
|
def load_session(path: str | Path) -> Session:
|
|
710
749
|
"""Load session from file.
|
|
711
750
|
|
|
751
|
+
This function implements HMAC-SHA256 signature verification before deserializing
|
|
752
|
+
session data to protect against tampering and malicious file modifications.
|
|
753
|
+
|
|
754
|
+
Security:
|
|
755
|
+
Session files are protected with HMAC-SHA256 signatures. Only load session
|
|
756
|
+
files from trusted sources. While HMAC verification prevents tampering,
|
|
757
|
+
the shared security key means all installations can verify each other's
|
|
758
|
+
files. Consider using per-installation keys for sensitive deployments.
|
|
759
|
+
|
|
712
760
|
Args:
|
|
713
761
|
path: Path to session file (.tks).
|
|
714
762
|
|
|
@@ -339,7 +339,11 @@ def _create_metric_plot(
|
|
|
339
339
|
plot_file.parent.mkdir(parents=True, exist_ok=True)
|
|
340
340
|
plt.savefig(plot_file)
|
|
341
341
|
else:
|
|
342
|
-
|
|
342
|
+
# Try to show, but gracefully handle non-interactive backends
|
|
343
|
+
try:
|
|
344
|
+
plt.show()
|
|
345
|
+
except Exception:
|
|
346
|
+
pass # Silently skip if backend doesn't support interactive display
|
|
343
347
|
|
|
344
348
|
|
|
345
349
|
def _plot_histogram(
|