mscs 2.2.0__tar.gz
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.
- mscs-2.2.0/LICENSE +21 -0
- mscs-2.2.0/PKG-INFO +234 -0
- mscs-2.2.0/README.md +197 -0
- mscs-2.2.0/pyproject.toml +52 -0
- mscs-2.2.0/src/mscs/__init__.py +50 -0
- mscs-2.2.0/src/mscs/_core.py +1031 -0
- mscs-2.2.0/src/mscs/py.typed +0 -0
mscs-2.2.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Esraderey
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
mscs-2.2.0/PKG-INFO
ADDED
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: mscs
|
|
3
|
+
Version: 2.2.0
|
|
4
|
+
Summary: Safe, fast serialization for Python — a secure replacement for pickle with native support for numpy arrays and PyTorch tensors.
|
|
5
|
+
Project-URL: Homepage, https://github.com/esraderey/mscs
|
|
6
|
+
Project-URL: Repository, https://github.com/esraderey/mscs
|
|
7
|
+
Project-URL: Issues, https://github.com/esraderey/mscs/issues
|
|
8
|
+
Author: Esraderey
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: binary,checkpoint,fast,numpy,pickle,pytorch,safe,secure,serialization,tensor
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Intended Audience :: Science/Research
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Classifier: Operating System :: OS Independent
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
23
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
24
|
+
Classifier: Topic :: Security
|
|
25
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
26
|
+
Classifier: Typing :: Typed
|
|
27
|
+
Requires-Python: >=3.9
|
|
28
|
+
Provides-Extra: all
|
|
29
|
+
Requires-Dist: numpy>=1.20; extra == 'all'
|
|
30
|
+
Requires-Dist: torch>=2.0; extra == 'all'
|
|
31
|
+
Provides-Extra: numpy
|
|
32
|
+
Requires-Dist: numpy>=1.20; extra == 'numpy'
|
|
33
|
+
Provides-Extra: torch
|
|
34
|
+
Requires-Dist: numpy>=1.20; extra == 'torch'
|
|
35
|
+
Requires-Dist: torch>=2.0; extra == 'torch'
|
|
36
|
+
Description-Content-Type: text/markdown
|
|
37
|
+
|
|
38
|
+
# MSCS — Safe Serialization for Python
|
|
39
|
+
|
|
40
|
+
A secure, fast, binary serialization library. Drop-in replacement for `pickle` that **never executes arbitrary code** during deserialization.
|
|
41
|
+
|
|
42
|
+
Built for AI/ML workflows — native support for **NumPy arrays** and **PyTorch tensors** with zero-copy performance.
|
|
43
|
+
|
|
44
|
+
## Why not pickle?
|
|
45
|
+
|
|
46
|
+
```python
|
|
47
|
+
# pickle: arbitrary code execution on load
|
|
48
|
+
data = pickle.loads(untrusted_bytes) # can run os.system("rm -rf /")
|
|
49
|
+
|
|
50
|
+
# mscs: only reconstructs explicitly registered classes
|
|
51
|
+
data = mscs.loads(untrusted_bytes) # MSCSecurityError if class not registered
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Install
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
pip install mscs # core (no dependencies)
|
|
58
|
+
pip install mscs[numpy] # + numpy support
|
|
59
|
+
pip install mscs[torch] # + numpy + PyTorch tensor support
|
|
60
|
+
pip install mscs[all] # everything
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Quick Start
|
|
64
|
+
|
|
65
|
+
```python
|
|
66
|
+
import mscs
|
|
67
|
+
|
|
68
|
+
# Primitives, collections, nested structures — just works
|
|
69
|
+
data = {"model": "v5.2", "lr": 0.001, "layers": [64, 128, 256]}
|
|
70
|
+
encoded = mscs.dumps(data)
|
|
71
|
+
decoded = mscs.loads(encoded)
|
|
72
|
+
|
|
73
|
+
# NumPy arrays
|
|
74
|
+
import numpy as np
|
|
75
|
+
arr = np.random.randn(100, 100).astype(np.float32)
|
|
76
|
+
encoded = mscs.dumps(arr) # 39 KB (vs 39.5 KB pickle)
|
|
77
|
+
|
|
78
|
+
# PyTorch tensors — no .numpy() conversion needed
|
|
79
|
+
import torch
|
|
80
|
+
weights = torch.randn(256, 256)
|
|
81
|
+
encoded = mscs.dumps(weights) # safe, no pickle involved
|
|
82
|
+
|
|
83
|
+
# Full model checkpoints
|
|
84
|
+
checkpoint = {
|
|
85
|
+
"epoch": 100,
|
|
86
|
+
"model_state": {k: v for k, v in model.state_dict().items()},
|
|
87
|
+
"optimizer_lr": 0.0003,
|
|
88
|
+
}
|
|
89
|
+
mscs.dump(checkpoint, open("checkpoint.mscs", "wb"))
|
|
90
|
+
restored = mscs.load(open("checkpoint.mscs", "rb"))
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## Custom Classes
|
|
94
|
+
|
|
95
|
+
```python
|
|
96
|
+
import mscs
|
|
97
|
+
from dataclasses import dataclass
|
|
98
|
+
|
|
99
|
+
@mscs.register
|
|
100
|
+
@dataclass
|
|
101
|
+
class Config:
|
|
102
|
+
state_size: int = 256
|
|
103
|
+
lr: float = 0.001
|
|
104
|
+
|
|
105
|
+
config = Config(512, 0.0003)
|
|
106
|
+
data = mscs.dumps(config)
|
|
107
|
+
restored = mscs.loads(data) # Config(state_size=512, lr=0.0003)
|
|
108
|
+
|
|
109
|
+
# Unregistered classes raise MSCSecurityError in strict mode
|
|
110
|
+
mscs.loads(data_with_unknown_class) # MSCSecurityError
|
|
111
|
+
|
|
112
|
+
# Or get a dict fallback in non-strict mode
|
|
113
|
+
mscs.loads(data_with_unknown_class, strict=False) # {'__class__': '...', '__state__': {...}}
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### Backward Compatibility with Renamed Classes
|
|
117
|
+
|
|
118
|
+
```python
|
|
119
|
+
# Class was renamed from OldConfig to Config
|
|
120
|
+
mscs.register_alias("my_module.OldConfig", Config)
|
|
121
|
+
# Old checkpoints now deserialize correctly
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### Register All Classes in a Module
|
|
125
|
+
|
|
126
|
+
```python
|
|
127
|
+
import my_models
|
|
128
|
+
mscs.register_module(my_models) # registers all classes defined in the module
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
## Compression & Integrity
|
|
132
|
+
|
|
133
|
+
```python
|
|
134
|
+
# zlib compression
|
|
135
|
+
with open("data.mscs.z", "wb") as f:
|
|
136
|
+
mscs.dump_compressed(large_obj, f)
|
|
137
|
+
|
|
138
|
+
with open("data.mscs.z", "rb") as f:
|
|
139
|
+
obj = mscs.load_compressed(f)
|
|
140
|
+
|
|
141
|
+
# CRC32 integrity check
|
|
142
|
+
data = mscs.dumps(obj, with_crc=True)
|
|
143
|
+
mscs.loads(data) # verifies CRC, raises MSCDecodeError if corrupted
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
## API Reference
|
|
147
|
+
|
|
148
|
+
### Core
|
|
149
|
+
|
|
150
|
+
| Function | Description |
|
|
151
|
+
|----------|------------|
|
|
152
|
+
| `dumps(obj, *, with_crc=False) -> bytes` | Serialize to bytes |
|
|
153
|
+
| `loads(data, *, strict=True) -> Any` | Deserialize from bytes |
|
|
154
|
+
| `dump(obj, file, **kwargs)` | Serialize to file (binary mode) |
|
|
155
|
+
| `load(file, **kwargs) -> Any` | Deserialize from file |
|
|
156
|
+
| `dump_compressed(obj, file, level=6)` | Serialize with zlib compression |
|
|
157
|
+
| `load_compressed(file) -> Any` | Deserialize compressed data |
|
|
158
|
+
|
|
159
|
+
### Registry
|
|
160
|
+
|
|
161
|
+
| Function | Description |
|
|
162
|
+
|----------|------------|
|
|
163
|
+
| `register(cls) -> cls` | Register class as safe (also works as decorator) |
|
|
164
|
+
| `register_alias(old_path, cls)` | Map old class path to new class |
|
|
165
|
+
| `register_module(module) -> list` | Register all classes in a module |
|
|
166
|
+
|
|
167
|
+
### Utilities
|
|
168
|
+
|
|
169
|
+
| Function | Description |
|
|
170
|
+
|----------|------------|
|
|
171
|
+
| `inspect(data) -> dict` | Get metadata without deserializing |
|
|
172
|
+
| `benchmark(obj, rounds=100) -> dict` | Measure encode/decode performance |
|
|
173
|
+
| `copy(obj) -> obj` | Deep copy via serialization round-trip |
|
|
174
|
+
|
|
175
|
+
## Supported Types
|
|
176
|
+
|
|
177
|
+
| Type | Tag | Notes |
|
|
178
|
+
|------|-----|-------|
|
|
179
|
+
| `None`, `bool`, `int`, `float`, `complex` | Built-in | Arbitrary precision ints |
|
|
180
|
+
| `str`, `bytes`, `bytearray` | Built-in | UTF-8, ref-tracked |
|
|
181
|
+
| `list`, `tuple`, `dict`, `set`, `frozenset` | Built-in | Circular refs supported |
|
|
182
|
+
| `datetime`, `date`, `time`, `timedelta` | Built-in | ISO 8601 |
|
|
183
|
+
| `Decimal`, `UUID`, `Path` | Built-in | Lossless |
|
|
184
|
+
| `Enum` | Registry | Must be registered |
|
|
185
|
+
| `numpy.ndarray` | Built-in | dtype whitelist enforced |
|
|
186
|
+
| `torch.Tensor` | Built-in | Auto CPU transfer, preserves requires_grad |
|
|
187
|
+
| `dataclass`, `__slots__`, `__dict__` | Registry | Must be registered |
|
|
188
|
+
|
|
189
|
+
## Performance
|
|
190
|
+
|
|
191
|
+
Benchmarked on a state_dict with 4 tensors (~57K parameters):
|
|
192
|
+
|
|
193
|
+
| Method | Roundtrip | Size | Safe |
|
|
194
|
+
|--------|-----------|------|------|
|
|
195
|
+
| **mscs** | **0.098 ms** | **65 KB** | **Yes** |
|
|
196
|
+
| pickle | 0.580 ms | 68 KB | No (RCE) |
|
|
197
|
+
| torch.save | 0.437 ms | 67 KB | No (RCE) |
|
|
198
|
+
|
|
199
|
+
**5.9x faster than pickle, 4.1x faster than torch.save** — while being the only option that doesn't allow arbitrary code execution.
|
|
200
|
+
|
|
201
|
+
Tensor scaling (encode+decode):
|
|
202
|
+
|
|
203
|
+
| Shape | mscs | pickle | Speedup |
|
|
204
|
+
|-------|------|--------|---------|
|
|
205
|
+
| 10x10 | 0.019 ms | 0.156 ms | 8.2x |
|
|
206
|
+
| 256x256 | 0.069 ms | 0.278 ms | 4.0x |
|
|
207
|
+
| 1024x1024 | 4.7 ms | 5.0 ms | 1.1x |
|
|
208
|
+
|
|
209
|
+
## Security Model
|
|
210
|
+
|
|
211
|
+
1. **No code execution**: Deserialization only reconstructs data, never runs arbitrary code
|
|
212
|
+
2. **Explicit registry**: Custom classes must be registered before deserialization
|
|
213
|
+
3. **No dynamic imports**: Class names in the binary stream are only used as registry keys
|
|
214
|
+
4. **NumPy dtype whitelist**: Blocks `object`, `void`, and structured dtypes
|
|
215
|
+
5. **Configurable limits**: `MAX_DEPTH=256`, `MAX_SIZE=512MB`, `MAX_COLLECTION=10M`
|
|
216
|
+
6. **Anti zip-bomb**: `load_compressed` validates both compressed and decompressed sizes
|
|
217
|
+
7. **CRC32 integrity**: Optional checksum to detect corruption
|
|
218
|
+
8. **Auditable format**: Magic bytes (`MSCS`) + version byte + type tags
|
|
219
|
+
|
|
220
|
+
## Binary Format
|
|
221
|
+
|
|
222
|
+
```
|
|
223
|
+
[MSCS][version:1][flags:1][type_tag:1][...payload...]
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
- Magic: `MSCS` (4 bytes)
|
|
227
|
+
- Version: `\x02` (1 byte)
|
|
228
|
+
- Flags: bit 0 = CRC32 appended (1 byte)
|
|
229
|
+
- Payload: recursive type-tagged binary data
|
|
230
|
+
- Optional CRC32 trailer (4 bytes)
|
|
231
|
+
|
|
232
|
+
## License
|
|
233
|
+
|
|
234
|
+
MIT
|
mscs-2.2.0/README.md
ADDED
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
# MSCS — Safe Serialization for Python
|
|
2
|
+
|
|
3
|
+
A secure, fast, binary serialization library. Drop-in replacement for `pickle` that **never executes arbitrary code** during deserialization.
|
|
4
|
+
|
|
5
|
+
Built for AI/ML workflows — native support for **NumPy arrays** and **PyTorch tensors** with zero-copy performance.
|
|
6
|
+
|
|
7
|
+
## Why not pickle?
|
|
8
|
+
|
|
9
|
+
```python
|
|
10
|
+
# pickle: arbitrary code execution on load
|
|
11
|
+
data = pickle.loads(untrusted_bytes) # can run os.system("rm -rf /")
|
|
12
|
+
|
|
13
|
+
# mscs: only reconstructs explicitly registered classes
|
|
14
|
+
data = mscs.loads(untrusted_bytes) # MSCSecurityError if class not registered
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Install
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
pip install mscs # core (no dependencies)
|
|
21
|
+
pip install mscs[numpy] # + numpy support
|
|
22
|
+
pip install mscs[torch] # + numpy + PyTorch tensor support
|
|
23
|
+
pip install mscs[all] # everything
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Quick Start
|
|
27
|
+
|
|
28
|
+
```python
|
|
29
|
+
import mscs
|
|
30
|
+
|
|
31
|
+
# Primitives, collections, nested structures — just works
|
|
32
|
+
data = {"model": "v5.2", "lr": 0.001, "layers": [64, 128, 256]}
|
|
33
|
+
encoded = mscs.dumps(data)
|
|
34
|
+
decoded = mscs.loads(encoded)
|
|
35
|
+
|
|
36
|
+
# NumPy arrays
|
|
37
|
+
import numpy as np
|
|
38
|
+
arr = np.random.randn(100, 100).astype(np.float32)
|
|
39
|
+
encoded = mscs.dumps(arr) # 39 KB (vs 39.5 KB pickle)
|
|
40
|
+
|
|
41
|
+
# PyTorch tensors — no .numpy() conversion needed
|
|
42
|
+
import torch
|
|
43
|
+
weights = torch.randn(256, 256)
|
|
44
|
+
encoded = mscs.dumps(weights) # safe, no pickle involved
|
|
45
|
+
|
|
46
|
+
# Full model checkpoints
|
|
47
|
+
checkpoint = {
|
|
48
|
+
"epoch": 100,
|
|
49
|
+
"model_state": {k: v for k, v in model.state_dict().items()},
|
|
50
|
+
"optimizer_lr": 0.0003,
|
|
51
|
+
}
|
|
52
|
+
mscs.dump(checkpoint, open("checkpoint.mscs", "wb"))
|
|
53
|
+
restored = mscs.load(open("checkpoint.mscs", "rb"))
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Custom Classes
|
|
57
|
+
|
|
58
|
+
```python
|
|
59
|
+
import mscs
|
|
60
|
+
from dataclasses import dataclass
|
|
61
|
+
|
|
62
|
+
@mscs.register
|
|
63
|
+
@dataclass
|
|
64
|
+
class Config:
|
|
65
|
+
state_size: int = 256
|
|
66
|
+
lr: float = 0.001
|
|
67
|
+
|
|
68
|
+
config = Config(512, 0.0003)
|
|
69
|
+
data = mscs.dumps(config)
|
|
70
|
+
restored = mscs.loads(data) # Config(state_size=512, lr=0.0003)
|
|
71
|
+
|
|
72
|
+
# Unregistered classes raise MSCSecurityError in strict mode
|
|
73
|
+
mscs.loads(data_with_unknown_class) # MSCSecurityError
|
|
74
|
+
|
|
75
|
+
# Or get a dict fallback in non-strict mode
|
|
76
|
+
mscs.loads(data_with_unknown_class, strict=False) # {'__class__': '...', '__state__': {...}}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Backward Compatibility with Renamed Classes
|
|
80
|
+
|
|
81
|
+
```python
|
|
82
|
+
# Class was renamed from OldConfig to Config
|
|
83
|
+
mscs.register_alias("my_module.OldConfig", Config)
|
|
84
|
+
# Old checkpoints now deserialize correctly
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
### Register All Classes in a Module
|
|
88
|
+
|
|
89
|
+
```python
|
|
90
|
+
import my_models
|
|
91
|
+
mscs.register_module(my_models) # registers all classes defined in the module
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## Compression & Integrity
|
|
95
|
+
|
|
96
|
+
```python
|
|
97
|
+
# zlib compression
|
|
98
|
+
with open("data.mscs.z", "wb") as f:
|
|
99
|
+
mscs.dump_compressed(large_obj, f)
|
|
100
|
+
|
|
101
|
+
with open("data.mscs.z", "rb") as f:
|
|
102
|
+
obj = mscs.load_compressed(f)
|
|
103
|
+
|
|
104
|
+
# CRC32 integrity check
|
|
105
|
+
data = mscs.dumps(obj, with_crc=True)
|
|
106
|
+
mscs.loads(data) # verifies CRC, raises MSCDecodeError if corrupted
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## API Reference
|
|
110
|
+
|
|
111
|
+
### Core
|
|
112
|
+
|
|
113
|
+
| Function | Description |
|
|
114
|
+
|----------|------------|
|
|
115
|
+
| `dumps(obj, *, with_crc=False) -> bytes` | Serialize to bytes |
|
|
116
|
+
| `loads(data, *, strict=True) -> Any` | Deserialize from bytes |
|
|
117
|
+
| `dump(obj, file, **kwargs)` | Serialize to file (binary mode) |
|
|
118
|
+
| `load(file, **kwargs) -> Any` | Deserialize from file |
|
|
119
|
+
| `dump_compressed(obj, file, level=6)` | Serialize with zlib compression |
|
|
120
|
+
| `load_compressed(file) -> Any` | Deserialize compressed data |
|
|
121
|
+
|
|
122
|
+
### Registry
|
|
123
|
+
|
|
124
|
+
| Function | Description |
|
|
125
|
+
|----------|------------|
|
|
126
|
+
| `register(cls) -> cls` | Register class as safe (also works as decorator) |
|
|
127
|
+
| `register_alias(old_path, cls)` | Map old class path to new class |
|
|
128
|
+
| `register_module(module) -> list` | Register all classes in a module |
|
|
129
|
+
|
|
130
|
+
### Utilities
|
|
131
|
+
|
|
132
|
+
| Function | Description |
|
|
133
|
+
|----------|------------|
|
|
134
|
+
| `inspect(data) -> dict` | Get metadata without deserializing |
|
|
135
|
+
| `benchmark(obj, rounds=100) -> dict` | Measure encode/decode performance |
|
|
136
|
+
| `copy(obj) -> obj` | Deep copy via serialization round-trip |
|
|
137
|
+
|
|
138
|
+
## Supported Types
|
|
139
|
+
|
|
140
|
+
| Type | Tag | Notes |
|
|
141
|
+
|------|-----|-------|
|
|
142
|
+
| `None`, `bool`, `int`, `float`, `complex` | Built-in | Arbitrary precision ints |
|
|
143
|
+
| `str`, `bytes`, `bytearray` | Built-in | UTF-8, ref-tracked |
|
|
144
|
+
| `list`, `tuple`, `dict`, `set`, `frozenset` | Built-in | Circular refs supported |
|
|
145
|
+
| `datetime`, `date`, `time`, `timedelta` | Built-in | ISO 8601 |
|
|
146
|
+
| `Decimal`, `UUID`, `Path` | Built-in | Lossless |
|
|
147
|
+
| `Enum` | Registry | Must be registered |
|
|
148
|
+
| `numpy.ndarray` | Built-in | dtype whitelist enforced |
|
|
149
|
+
| `torch.Tensor` | Built-in | Auto CPU transfer, preserves requires_grad |
|
|
150
|
+
| `dataclass`, `__slots__`, `__dict__` | Registry | Must be registered |
|
|
151
|
+
|
|
152
|
+
## Performance
|
|
153
|
+
|
|
154
|
+
Benchmarked on a state_dict with 4 tensors (~57K parameters):
|
|
155
|
+
|
|
156
|
+
| Method | Roundtrip | Size | Safe |
|
|
157
|
+
|--------|-----------|------|------|
|
|
158
|
+
| **mscs** | **0.098 ms** | **65 KB** | **Yes** |
|
|
159
|
+
| pickle | 0.580 ms | 68 KB | No (RCE) |
|
|
160
|
+
| torch.save | 0.437 ms | 67 KB | No (RCE) |
|
|
161
|
+
|
|
162
|
+
**5.9x faster than pickle, 4.1x faster than torch.save** — while being the only option that doesn't allow arbitrary code execution.
|
|
163
|
+
|
|
164
|
+
Tensor scaling (encode+decode):
|
|
165
|
+
|
|
166
|
+
| Shape | mscs | pickle | Speedup |
|
|
167
|
+
|-------|------|--------|---------|
|
|
168
|
+
| 10x10 | 0.019 ms | 0.156 ms | 8.2x |
|
|
169
|
+
| 256x256 | 0.069 ms | 0.278 ms | 4.0x |
|
|
170
|
+
| 1024x1024 | 4.7 ms | 5.0 ms | 1.1x |
|
|
171
|
+
|
|
172
|
+
## Security Model
|
|
173
|
+
|
|
174
|
+
1. **No code execution**: Deserialization only reconstructs data, never runs arbitrary code
|
|
175
|
+
2. **Explicit registry**: Custom classes must be registered before deserialization
|
|
176
|
+
3. **No dynamic imports**: Class names in the binary stream are only used as registry keys
|
|
177
|
+
4. **NumPy dtype whitelist**: Blocks `object`, `void`, and structured dtypes
|
|
178
|
+
5. **Configurable limits**: `MAX_DEPTH=256`, `MAX_SIZE=512MB`, `MAX_COLLECTION=10M`
|
|
179
|
+
6. **Anti zip-bomb**: `load_compressed` validates both compressed and decompressed sizes
|
|
180
|
+
7. **CRC32 integrity**: Optional checksum to detect corruption
|
|
181
|
+
8. **Auditable format**: Magic bytes (`MSCS`) + version byte + type tags
|
|
182
|
+
|
|
183
|
+
## Binary Format
|
|
184
|
+
|
|
185
|
+
```
|
|
186
|
+
[MSCS][version:1][flags:1][type_tag:1][...payload...]
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
- Magic: `MSCS` (4 bytes)
|
|
190
|
+
- Version: `\x02` (1 byte)
|
|
191
|
+
- Flags: bit 0 = CRC32 appended (1 byte)
|
|
192
|
+
- Payload: recursive type-tagged binary data
|
|
193
|
+
- Optional CRC32 trailer (4 bytes)
|
|
194
|
+
|
|
195
|
+
## License
|
|
196
|
+
|
|
197
|
+
MIT
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "mscs"
|
|
7
|
+
version = "2.2.0"
|
|
8
|
+
description = "Safe, fast serialization for Python — a secure replacement for pickle with native support for numpy arrays and PyTorch tensors."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = "MIT"
|
|
11
|
+
requires-python = ">=3.9"
|
|
12
|
+
authors = [
|
|
13
|
+
{ name = "Esraderey" },
|
|
14
|
+
]
|
|
15
|
+
keywords = [
|
|
16
|
+
"serialization", "pickle", "safe", "secure",
|
|
17
|
+
"numpy", "pytorch", "tensor", "checkpoint",
|
|
18
|
+
"binary", "fast",
|
|
19
|
+
]
|
|
20
|
+
classifiers = [
|
|
21
|
+
"Development Status :: 4 - Beta",
|
|
22
|
+
"Intended Audience :: Developers",
|
|
23
|
+
"Intended Audience :: Science/Research",
|
|
24
|
+
"License :: OSI Approved :: MIT License",
|
|
25
|
+
"Operating System :: OS Independent",
|
|
26
|
+
"Programming Language :: Python :: 3",
|
|
27
|
+
"Programming Language :: Python :: 3.9",
|
|
28
|
+
"Programming Language :: Python :: 3.10",
|
|
29
|
+
"Programming Language :: Python :: 3.11",
|
|
30
|
+
"Programming Language :: Python :: 3.12",
|
|
31
|
+
"Programming Language :: Python :: 3.13",
|
|
32
|
+
"Topic :: Scientific/Engineering :: Artificial Intelligence",
|
|
33
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
34
|
+
"Topic :: Security",
|
|
35
|
+
"Typing :: Typed",
|
|
36
|
+
]
|
|
37
|
+
|
|
38
|
+
[project.optional-dependencies]
|
|
39
|
+
numpy = ["numpy>=1.20"]
|
|
40
|
+
torch = ["numpy>=1.20", "torch>=2.0"]
|
|
41
|
+
all = ["numpy>=1.20", "torch>=2.0"]
|
|
42
|
+
|
|
43
|
+
[project.urls]
|
|
44
|
+
Homepage = "https://github.com/esraderey/mscs"
|
|
45
|
+
Repository = "https://github.com/esraderey/mscs"
|
|
46
|
+
Issues = "https://github.com/esraderey/mscs/issues"
|
|
47
|
+
|
|
48
|
+
[tool.hatch.build.targets.sdist]
|
|
49
|
+
include = ["src/mscs/"]
|
|
50
|
+
|
|
51
|
+
[tool.hatch.build.targets.wheel]
|
|
52
|
+
packages = ["src/mscs"]
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"""
|
|
2
|
+
MSCS — Safe serialization for Python. A secure, fast replacement for pickle.
|
|
3
|
+
|
|
4
|
+
Usage:
|
|
5
|
+
import mscs
|
|
6
|
+
|
|
7
|
+
data = mscs.dumps(obj)
|
|
8
|
+
obj = mscs.loads(data)
|
|
9
|
+
|
|
10
|
+
mscs.register(MyClass) # allow deserialization of custom classes
|
|
11
|
+
"""
|
|
12
|
+
from mscs._core import (
|
|
13
|
+
# Version
|
|
14
|
+
__version__,
|
|
15
|
+
# Public API
|
|
16
|
+
dump,
|
|
17
|
+
load,
|
|
18
|
+
dumps,
|
|
19
|
+
loads,
|
|
20
|
+
dump_compressed,
|
|
21
|
+
load_compressed,
|
|
22
|
+
register,
|
|
23
|
+
register_alias,
|
|
24
|
+
register_module,
|
|
25
|
+
inspect,
|
|
26
|
+
benchmark,
|
|
27
|
+
copy,
|
|
28
|
+
# Exceptions
|
|
29
|
+
MSCError,
|
|
30
|
+
MSCEncodeError,
|
|
31
|
+
MSCDecodeError,
|
|
32
|
+
MSCSecurityError,
|
|
33
|
+
# Constants (for advanced users)
|
|
34
|
+
MAGIC,
|
|
35
|
+
VERSION,
|
|
36
|
+
MAX_DEPTH,
|
|
37
|
+
MAX_SIZE,
|
|
38
|
+
MAX_COMPRESSED,
|
|
39
|
+
MAX_COLLECTION,
|
|
40
|
+
MAX_STRING,
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
__all__ = [
|
|
44
|
+
"__version__",
|
|
45
|
+
"dump", "load", "dumps", "loads",
|
|
46
|
+
"dump_compressed", "load_compressed",
|
|
47
|
+
"register", "register_alias", "register_module",
|
|
48
|
+
"inspect", "benchmark", "copy",
|
|
49
|
+
"MSCError", "MSCEncodeError", "MSCDecodeError", "MSCSecurityError",
|
|
50
|
+
]
|