mscs 2.2.0__tar.gz → 2.3.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.3.0/.gitignore +207 -0
- mscs-2.3.0/PKG-INFO +315 -0
- mscs-2.3.0/README.md +274 -0
- {mscs-2.2.0 → mscs-2.3.0}/pyproject.toml +9 -7
- {mscs-2.2.0 → mscs-2.3.0}/src/mscs/__init__.py +2 -0
- {mscs-2.2.0 → mscs-2.3.0}/src/mscs/_core.py +220 -92
- mscs-2.2.0/PKG-INFO +0 -234
- mscs-2.2.0/README.md +0 -197
- {mscs-2.2.0 → mscs-2.3.0}/LICENSE +0 -0
- {mscs-2.2.0 → mscs-2.3.0}/src/mscs/py.typed +0 -0
mscs-2.3.0/.gitignore
ADDED
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
# Byte-compiled / optimized / DLL files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[codz]
|
|
4
|
+
*$py.class
|
|
5
|
+
|
|
6
|
+
# C extensions
|
|
7
|
+
*.so
|
|
8
|
+
|
|
9
|
+
# Distribution / packaging
|
|
10
|
+
.Python
|
|
11
|
+
build/
|
|
12
|
+
develop-eggs/
|
|
13
|
+
dist/
|
|
14
|
+
downloads/
|
|
15
|
+
eggs/
|
|
16
|
+
.eggs/
|
|
17
|
+
lib/
|
|
18
|
+
lib64/
|
|
19
|
+
parts/
|
|
20
|
+
sdist/
|
|
21
|
+
var/
|
|
22
|
+
wheels/
|
|
23
|
+
share/python-wheels/
|
|
24
|
+
*.egg-info/
|
|
25
|
+
.installed.cfg
|
|
26
|
+
*.egg
|
|
27
|
+
MANIFEST
|
|
28
|
+
|
|
29
|
+
# PyInstaller
|
|
30
|
+
# Usually these files are written by a python script from a template
|
|
31
|
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
|
32
|
+
*.manifest
|
|
33
|
+
*.spec
|
|
34
|
+
|
|
35
|
+
# Installer logs
|
|
36
|
+
pip-log.txt
|
|
37
|
+
pip-delete-this-directory.txt
|
|
38
|
+
|
|
39
|
+
# Unit test / coverage reports
|
|
40
|
+
htmlcov/
|
|
41
|
+
.tox/
|
|
42
|
+
.nox/
|
|
43
|
+
.coverage
|
|
44
|
+
.coverage.*
|
|
45
|
+
.cache
|
|
46
|
+
nosetests.xml
|
|
47
|
+
coverage.xml
|
|
48
|
+
*.cover
|
|
49
|
+
*.py.cover
|
|
50
|
+
.hypothesis/
|
|
51
|
+
.pytest_cache/
|
|
52
|
+
cover/
|
|
53
|
+
|
|
54
|
+
# Translations
|
|
55
|
+
*.mo
|
|
56
|
+
*.pot
|
|
57
|
+
|
|
58
|
+
# Django stuff:
|
|
59
|
+
*.log
|
|
60
|
+
local_settings.py
|
|
61
|
+
db.sqlite3
|
|
62
|
+
db.sqlite3-journal
|
|
63
|
+
|
|
64
|
+
# Flask stuff:
|
|
65
|
+
instance/
|
|
66
|
+
.webassets-cache
|
|
67
|
+
|
|
68
|
+
# Scrapy stuff:
|
|
69
|
+
.scrapy
|
|
70
|
+
|
|
71
|
+
# Sphinx documentation
|
|
72
|
+
docs/_build/
|
|
73
|
+
|
|
74
|
+
# PyBuilder
|
|
75
|
+
.pybuilder/
|
|
76
|
+
target/
|
|
77
|
+
|
|
78
|
+
# Jupyter Notebook
|
|
79
|
+
.ipynb_checkpoints
|
|
80
|
+
|
|
81
|
+
# IPython
|
|
82
|
+
profile_default/
|
|
83
|
+
ipython_config.py
|
|
84
|
+
|
|
85
|
+
# pyenv
|
|
86
|
+
# For a library or package, you might want to ignore these files since the code is
|
|
87
|
+
# intended to run in multiple environments; otherwise, check them in:
|
|
88
|
+
# .python-version
|
|
89
|
+
|
|
90
|
+
# pipenv
|
|
91
|
+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
|
92
|
+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
|
93
|
+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
|
94
|
+
# install all needed dependencies.
|
|
95
|
+
#Pipfile.lock
|
|
96
|
+
|
|
97
|
+
# UV
|
|
98
|
+
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
|
|
99
|
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
100
|
+
# commonly ignored for libraries.
|
|
101
|
+
#uv.lock
|
|
102
|
+
|
|
103
|
+
# poetry
|
|
104
|
+
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
|
105
|
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
106
|
+
# commonly ignored for libraries.
|
|
107
|
+
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
|
108
|
+
#poetry.lock
|
|
109
|
+
#poetry.toml
|
|
110
|
+
|
|
111
|
+
# pdm
|
|
112
|
+
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
|
113
|
+
# pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python.
|
|
114
|
+
# https://pdm-project.org/en/latest/usage/project/#working-with-version-control
|
|
115
|
+
#pdm.lock
|
|
116
|
+
#pdm.toml
|
|
117
|
+
.pdm-python
|
|
118
|
+
.pdm-build/
|
|
119
|
+
|
|
120
|
+
# pixi
|
|
121
|
+
# Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control.
|
|
122
|
+
#pixi.lock
|
|
123
|
+
# Pixi creates a virtual environment in the .pixi directory, just like venv module creates one
|
|
124
|
+
# in the .venv directory. It is recommended not to include this directory in version control.
|
|
125
|
+
.pixi
|
|
126
|
+
|
|
127
|
+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
|
128
|
+
__pypackages__/
|
|
129
|
+
|
|
130
|
+
# Celery stuff
|
|
131
|
+
celerybeat-schedule
|
|
132
|
+
celerybeat.pid
|
|
133
|
+
|
|
134
|
+
# SageMath parsed files
|
|
135
|
+
*.sage.py
|
|
136
|
+
|
|
137
|
+
# Environments
|
|
138
|
+
.env
|
|
139
|
+
.envrc
|
|
140
|
+
.venv
|
|
141
|
+
env/
|
|
142
|
+
venv/
|
|
143
|
+
ENV/
|
|
144
|
+
env.bak/
|
|
145
|
+
venv.bak/
|
|
146
|
+
|
|
147
|
+
# Spyder project settings
|
|
148
|
+
.spyderproject
|
|
149
|
+
.spyproject
|
|
150
|
+
|
|
151
|
+
# Rope project settings
|
|
152
|
+
.ropeproject
|
|
153
|
+
|
|
154
|
+
# mkdocs documentation
|
|
155
|
+
/site
|
|
156
|
+
|
|
157
|
+
# mypy
|
|
158
|
+
.mypy_cache/
|
|
159
|
+
.dmypy.json
|
|
160
|
+
dmypy.json
|
|
161
|
+
|
|
162
|
+
# Pyre type checker
|
|
163
|
+
.pyre/
|
|
164
|
+
|
|
165
|
+
# pytype static type analyzer
|
|
166
|
+
.pytype/
|
|
167
|
+
|
|
168
|
+
# Cython debug symbols
|
|
169
|
+
cython_debug/
|
|
170
|
+
|
|
171
|
+
# PyCharm
|
|
172
|
+
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
|
173
|
+
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
|
174
|
+
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
|
175
|
+
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
|
176
|
+
#.idea/
|
|
177
|
+
|
|
178
|
+
# Abstra
|
|
179
|
+
# Abstra is an AI-powered process automation framework.
|
|
180
|
+
# Ignore directories containing user credentials, local state, and settings.
|
|
181
|
+
# Learn more at https://abstra.io/docs
|
|
182
|
+
.abstra/
|
|
183
|
+
|
|
184
|
+
# Visual Studio Code
|
|
185
|
+
# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
|
|
186
|
+
# that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
|
|
187
|
+
# and can be added to the global gitignore or merged into this file. However, if you prefer,
|
|
188
|
+
# you could uncomment the following to ignore the entire vscode folder
|
|
189
|
+
# .vscode/
|
|
190
|
+
|
|
191
|
+
# Ruff stuff:
|
|
192
|
+
.ruff_cache/
|
|
193
|
+
|
|
194
|
+
# PyPI configuration file
|
|
195
|
+
.pypirc
|
|
196
|
+
|
|
197
|
+
# Cursor
|
|
198
|
+
# Cursor is an AI-powered code editor. `.cursorignore` specifies files/directories to
|
|
199
|
+
# exclude from AI features like autocomplete and code analysis. Recommended for sensitive data
|
|
200
|
+
# refer to https://docs.cursor.com/context/ignore-files
|
|
201
|
+
.cursorignore
|
|
202
|
+
.cursorindexingignore
|
|
203
|
+
|
|
204
|
+
# Marimo
|
|
205
|
+
marimo/_static/
|
|
206
|
+
marimo/_lsp/
|
|
207
|
+
__marimo__/
|
mscs-2.3.0/PKG-INFO
ADDED
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: mscs
|
|
3
|
+
Version: 2.3.0
|
|
4
|
+
Summary: Safe, fast serialization for Python — a secure replacement for pickle with HMAC authentication, native numpy/PyTorch support.
|
|
5
|
+
Project-URL: Homepage, https://github.com/ElEscribanoSilente/MSC-Serial
|
|
6
|
+
Project-URL: Repository, https://github.com/ElEscribanoSilente/MSC-Serial
|
|
7
|
+
Project-URL: Issues, https://github.com/ElEscribanoSilente/MSC-Serial/issues
|
|
8
|
+
Project-URL: Changelog, https://github.com/ElEscribanoSilente/MSC-Serial/blob/main/CHANGELOG.md
|
|
9
|
+
Author: Esraderey
|
|
10
|
+
License-Expression: MIT
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Keywords: binary,checkpoint,fast,hmac,integrity,numpy,pickle,pytorch,safe,secure,serialization,tensor
|
|
13
|
+
Classifier: Development Status :: 4 - Beta
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: Intended Audience :: Science/Research
|
|
16
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
17
|
+
Classifier: Operating System :: OS Independent
|
|
18
|
+
Classifier: Programming Language :: Python :: 3
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
24
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
25
|
+
Classifier: Topic :: Security
|
|
26
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
27
|
+
Classifier: Typing :: Typed
|
|
28
|
+
Requires-Python: >=3.9
|
|
29
|
+
Provides-Extra: all
|
|
30
|
+
Requires-Dist: numpy>=1.20; extra == 'all'
|
|
31
|
+
Requires-Dist: torch>=2.0; extra == 'all'
|
|
32
|
+
Provides-Extra: numpy
|
|
33
|
+
Requires-Dist: numpy>=1.20; extra == 'numpy'
|
|
34
|
+
Provides-Extra: test
|
|
35
|
+
Requires-Dist: hypothesis>=6.0; extra == 'test'
|
|
36
|
+
Requires-Dist: pytest>=7.0; extra == 'test'
|
|
37
|
+
Provides-Extra: torch
|
|
38
|
+
Requires-Dist: numpy>=1.20; extra == 'torch'
|
|
39
|
+
Requires-Dist: torch>=2.0; extra == 'torch'
|
|
40
|
+
Description-Content-Type: text/markdown
|
|
41
|
+
|
|
42
|
+
# MSCS — Safe Serialization for Python
|
|
43
|
+
|
|
44
|
+
**v2.3.0** | [Changelog](CHANGELOG.md) | [PyPI](https://pypi.org/project/mscs/)
|
|
45
|
+
|
|
46
|
+
> **Status: Beta** — API is stable but the format may evolve. Not yet battle-tested in large-scale production.
|
|
47
|
+
|
|
48
|
+
A secure, fast, binary serialization library. Drop-in replacement for `pickle` that **does not execute arbitrary code** during deserialization of unregistered classes.
|
|
49
|
+
|
|
50
|
+
Built for AI/ML workflows — native support for **NumPy arrays** and **PyTorch tensors** with zero-copy performance.
|
|
51
|
+
|
|
52
|
+
## Why not pickle?
|
|
53
|
+
|
|
54
|
+
```python
|
|
55
|
+
# pickle: arbitrary code execution on load
|
|
56
|
+
data = pickle.loads(untrusted_bytes) # can run os.system("rm -rf /")
|
|
57
|
+
|
|
58
|
+
# mscs: only reconstructs explicitly registered classes
|
|
59
|
+
data = mscs.loads(untrusted_bytes) # MSCSecurityError if class not registered
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Comparison with Alternatives
|
|
63
|
+
|
|
64
|
+
| Feature | mscs | pickle | safetensors | torch.save |
|
|
65
|
+
|---------|------|--------|-------------|------------|
|
|
66
|
+
| No arbitrary code execution | Partial* | No | Yes | No |
|
|
67
|
+
| HMAC authentication | Yes | No | No | No |
|
|
68
|
+
| Custom class support | Yes (registry) | Yes | No | Yes |
|
|
69
|
+
| NumPy arrays | Yes | Yes | Yes | Yes |
|
|
70
|
+
| PyTorch tensors | Yes | Yes | Yes | Yes |
|
|
71
|
+
| Circular references | Yes | Yes | No | Yes |
|
|
72
|
+
| Zero dependencies | Yes | Yes | Yes (Rust) | No |
|
|
73
|
+
| Compression built-in | Yes (zlib) | No | No | No |
|
|
74
|
+
|
|
75
|
+
\* **mscs executes `__setstate__`** on registered classes. See [Security Model](#security-model) for details.
|
|
76
|
+
|
|
77
|
+
**When to use safetensors instead:** If you only need to serialize tensors and arrays (model weights, embeddings), [safetensors](https://github.com/huggingface/safetensors) is the industry standard — it's written in Rust, truly zero-code-execution, and widely adopted. Use mscs when you need to serialize **mixed Python objects** (configs, custom classes, nested structures) alongside tensors.
|
|
78
|
+
|
|
79
|
+
## Install
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
pip install mscs # core (no dependencies)
|
|
83
|
+
pip install mscs[numpy] # + numpy support
|
|
84
|
+
pip install mscs[torch] # + numpy + PyTorch tensor support
|
|
85
|
+
pip install mscs[all] # everything
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## Quick Start
|
|
89
|
+
|
|
90
|
+
```python
|
|
91
|
+
import mscs
|
|
92
|
+
|
|
93
|
+
# Primitives, collections, nested structures — just works
|
|
94
|
+
data = {"model": "v5.2", "lr": 0.001, "layers": [64, 128, 256]}
|
|
95
|
+
encoded = mscs.dumps(data)
|
|
96
|
+
decoded = mscs.loads(encoded)
|
|
97
|
+
|
|
98
|
+
# NumPy arrays
|
|
99
|
+
import numpy as np
|
|
100
|
+
arr = np.random.randn(100, 100).astype(np.float32)
|
|
101
|
+
encoded = mscs.dumps(arr)
|
|
102
|
+
|
|
103
|
+
# PyTorch tensors — no .numpy() conversion needed
|
|
104
|
+
import torch
|
|
105
|
+
weights = torch.randn(256, 256)
|
|
106
|
+
encoded = mscs.dumps(weights) # safe, no pickle involved
|
|
107
|
+
|
|
108
|
+
# Full model checkpoints
|
|
109
|
+
checkpoint = {
|
|
110
|
+
"epoch": 100,
|
|
111
|
+
"model_state": {k: v for k, v in model.state_dict().items()},
|
|
112
|
+
"optimizer_lr": 0.0003,
|
|
113
|
+
}
|
|
114
|
+
mscs.dump(checkpoint, open("checkpoint.mscs", "wb"))
|
|
115
|
+
restored = mscs.load(open("checkpoint.mscs", "rb"))
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## Custom Classes
|
|
119
|
+
|
|
120
|
+
```python
|
|
121
|
+
import mscs
|
|
122
|
+
from dataclasses import dataclass
|
|
123
|
+
|
|
124
|
+
@mscs.register
|
|
125
|
+
@dataclass
|
|
126
|
+
class Config:
|
|
127
|
+
state_size: int = 256
|
|
128
|
+
lr: float = 0.001
|
|
129
|
+
|
|
130
|
+
config = Config(512, 0.0003)
|
|
131
|
+
data = mscs.dumps(config)
|
|
132
|
+
restored = mscs.loads(data) # Config(state_size=512, lr=0.0003)
|
|
133
|
+
|
|
134
|
+
# Unregistered classes raise MSCSecurityError in strict mode
|
|
135
|
+
mscs.loads(data_with_unknown_class) # MSCSecurityError
|
|
136
|
+
|
|
137
|
+
# Or get a dict fallback in non-strict mode
|
|
138
|
+
mscs.loads(data_with_unknown_class, strict=False) # {'__class__': '...', '__state__': {...}}
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### Backward Compatibility with Renamed Classes
|
|
142
|
+
|
|
143
|
+
```python
|
|
144
|
+
mscs.register_alias("my_module.OldConfig", Config)
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### Register All Classes in a Module
|
|
148
|
+
|
|
149
|
+
```python
|
|
150
|
+
import my_models
|
|
151
|
+
mscs.register_module(my_models)
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
## Compression & Integrity
|
|
155
|
+
|
|
156
|
+
```python
|
|
157
|
+
# zlib compression
|
|
158
|
+
with open("data.mscs.z", "wb") as f:
|
|
159
|
+
mscs.dump_compressed(large_obj, f)
|
|
160
|
+
|
|
161
|
+
with open("data.mscs.z", "rb") as f:
|
|
162
|
+
obj = mscs.load_compressed(f)
|
|
163
|
+
|
|
164
|
+
# CRC32 integrity check (detects accidental corruption, NOT tamper-proof)
|
|
165
|
+
data = mscs.dumps(obj, with_crc=True)
|
|
166
|
+
mscs.loads(data) # verifies CRC, raises MSCDecodeError if corrupted
|
|
167
|
+
|
|
168
|
+
# HMAC-SHA256 authentication (cryptographic, tamper-proof)
|
|
169
|
+
key = b'your-secret-key-here'
|
|
170
|
+
data = mscs.dumps(obj, hmac_key=key)
|
|
171
|
+
mscs.loads(data, hmac_key=key) # verifies HMAC, raises MSCSecurityError if tampered
|
|
172
|
+
mscs.loads(data) # MSCSecurityError: no key provided for signed payload
|
|
173
|
+
mscs.loads(unsigned_data, hmac_key=key) # MSCSecurityError: anti-downgrade protection
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
## API Reference
|
|
177
|
+
|
|
178
|
+
### Core
|
|
179
|
+
|
|
180
|
+
| Function | Description |
|
|
181
|
+
|----------|------------|
|
|
182
|
+
| `dumps(obj, *, with_crc=False, hmac_key=None) -> bytes` | Serialize to bytes |
|
|
183
|
+
| `loads(data, *, strict=True, hmac_key=None) -> Any` | Deserialize from bytes |
|
|
184
|
+
| `dump(obj, file, *, with_crc=False, hmac_key=None)` | Serialize to file (binary mode) |
|
|
185
|
+
| `load(file, *, strict=True, hmac_key=None) -> Any` | Deserialize from file |
|
|
186
|
+
| `dump_compressed(obj, file, level=6)` | Serialize with zlib compression |
|
|
187
|
+
| `load_compressed(file) -> Any` | Deserialize compressed data |
|
|
188
|
+
|
|
189
|
+
### Registry
|
|
190
|
+
|
|
191
|
+
| Function | Description |
|
|
192
|
+
|----------|------------|
|
|
193
|
+
| `register(cls) -> cls` | Register class as safe (also works as decorator) |
|
|
194
|
+
| `register_alias(old_path, cls)` | Map old class path to new class |
|
|
195
|
+
| `register_module(module) -> list` | Register all classes in a module |
|
|
196
|
+
|
|
197
|
+
### Utilities
|
|
198
|
+
|
|
199
|
+
| Function | Description |
|
|
200
|
+
|----------|------------|
|
|
201
|
+
| `inspect(data) -> dict` | Get metadata without deserializing |
|
|
202
|
+
| `benchmark(obj, rounds=100) -> dict` | Measure encode/decode performance |
|
|
203
|
+
| `copy(obj) -> obj` | Deep copy via serialization round-trip |
|
|
204
|
+
|
|
205
|
+
## Supported Types
|
|
206
|
+
|
|
207
|
+
| Type | Notes |
|
|
208
|
+
|------|-------|
|
|
209
|
+
| `None`, `bool`, `int`, `float`, `complex` | Ints up to 8192 bytes (~19,700 digits) |
|
|
210
|
+
| `str`, `bytes`, `bytearray` | UTF-8, ref-tracked |
|
|
211
|
+
| `list`, `tuple`, `dict`, `set`, `frozenset` | Circular refs supported |
|
|
212
|
+
| `datetime`, `date`, `time`, `timedelta` | ISO 8601 |
|
|
213
|
+
| `Decimal`, `UUID`, `Path` | Lossless |
|
|
214
|
+
| `Enum` | Must be registered |
|
|
215
|
+
| `numpy.ndarray` | dtype whitelist enforced |
|
|
216
|
+
| `torch.Tensor` | Auto CPU transfer, preserves requires_grad |
|
|
217
|
+
| `dataclass`, `__slots__`, `__dict__` objects | Must be registered |
|
|
218
|
+
|
|
219
|
+
## Performance
|
|
220
|
+
|
|
221
|
+
Benchmarked on a single machine (results may vary by hardware and payload):
|
|
222
|
+
|
|
223
|
+
**Payload: `state_dict` with 4 tensors (~57K parameters, dominated by contiguous float32 buffers):**
|
|
224
|
+
|
|
225
|
+
| Method | Roundtrip | Size |
|
|
226
|
+
|--------|-----------|------|
|
|
227
|
+
| **mscs** | **~0.1 ms** | **~65 KB** |
|
|
228
|
+
| pickle | ~0.6 ms | ~68 KB |
|
|
229
|
+
| torch.save | ~0.4 ms | ~67 KB |
|
|
230
|
+
|
|
231
|
+
mscs is fast for tensor-heavy payloads because it writes raw buffers with minimal framing overhead. **For small, nested Python structures (dicts, strings, configs), the speedup is smaller.** Always benchmark with your actual data.
|
|
232
|
+
|
|
233
|
+
Run `python tests/benchmark.py` to reproduce on your machine.
|
|
234
|
+
|
|
235
|
+
## Security Model
|
|
236
|
+
|
|
237
|
+
mscs provides a **defense-in-depth** approach, but it is **not a sandbox**. Understand the boundaries:
|
|
238
|
+
|
|
239
|
+
### What mscs prevents
|
|
240
|
+
|
|
241
|
+
1. **No dynamic imports**: Class names in the binary stream are only used as registry lookup keys — never passed to `importlib`
|
|
242
|
+
2. **Explicit registry**: Custom classes must be registered before deserialization; unregistered classes raise `MSCSecurityError`
|
|
243
|
+
3. **NumPy dtype whitelist**: Blocks `object`, `void`, and structured dtypes that could execute code
|
|
244
|
+
4. **Configurable limits**: `MAX_DEPTH=256`, `MAX_SIZE=512MB`, `MAX_COLLECTION=10M`, `MAX_INT_BYTES=8192`
|
|
245
|
+
5. **Anti zip-bomb**: `load_compressed` validates both compressed and decompressed sizes with bounded reads
|
|
246
|
+
6. **Path null byte rejection**: Paths containing null bytes are rejected
|
|
247
|
+
7. **CRC32 corruption detection**: Optional checksum to detect accidental data corruption (not cryptographic — an attacker can forge CRC32)
|
|
248
|
+
8. **HMAC-SHA256 authentication**: Optional cryptographic signature to detect intentional tampering. Anti-downgrade protection prevents stripping the HMAC flag.
|
|
249
|
+
9. **Trailing bytes rejection**: Payloads with unexpected bytes after the serialized object are rejected
|
|
250
|
+
10. **Integer size limit**: Ints larger than `MAX_INT_BYTES` (8192 bytes, ~19,700 digits) are rejected to prevent CPU exhaustion attacks
|
|
251
|
+
|
|
252
|
+
### What mscs does NOT prevent
|
|
253
|
+
|
|
254
|
+
1. **`__setstate__` execution**: If you register a class that implements `__setstate__`, that method **will execute** during deserialization. Only register classes you trust.
|
|
255
|
+
2. **Path traversal**: Deserialized `Path` objects may contain `../` sequences. The consumer must validate paths before using them for file I/O.
|
|
256
|
+
3. **Malicious registered classes**: The security boundary is the registry. If you register a class with dangerous behavior in `__init__`, `__setstate__`, or property setters, mscs cannot protect you.
|
|
257
|
+
|
|
258
|
+
**Rule of thumb**: mscs is safe for deserializing untrusted *data* as long as your registry only contains trusted *classes*.
|
|
259
|
+
|
|
260
|
+
## Binary Format
|
|
261
|
+
|
|
262
|
+
```
|
|
263
|
+
┌──────────┬─────────┬───────┬──────────┬────────────────┬──────────────┬──────────────┐
|
|
264
|
+
│ Magic(4) │ Ver.(1) │ Fl(1) │ Tag(1) │ Payload(var) │ CRC32(4)? │ HMAC(32)? │
|
|
265
|
+
│ "MSCS" │ 0x02 │ bits │ type tag │ type-dependent │ if flag 0x01 │ if flag 0x02 │
|
|
266
|
+
└──────────┴─────────┴───────┴──────────┴────────────────┴──────────────┴──────────────┘
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
**Header** (6 bytes fixed):
|
|
270
|
+
- Bytes 0-3: Magic `MSCS` (0x4D534353)
|
|
271
|
+
- Byte 4: Format version (currently `0x02`)
|
|
272
|
+
- Byte 5: Flags (bit 0 = CRC32 appended, bit 1 = HMAC-SHA256 appended)
|
|
273
|
+
|
|
274
|
+
CRC32 and HMAC are mutually exclusive (HMAC is strictly superior).
|
|
275
|
+
|
|
276
|
+
**Payload**: Recursive type-length-value encoding. Each value starts with a 1-byte type tag:
|
|
277
|
+
|
|
278
|
+
| Tag | Type | Payload format |
|
|
279
|
+
|-----|------|----------------|
|
|
280
|
+
| 0x00 | None | (empty) |
|
|
281
|
+
| 0x01 | bool | 1 byte (0x00/0x01) |
|
|
282
|
+
| 0x02 | int | `<H>` byte count + signed little-endian bytes |
|
|
283
|
+
| 0x03 | float | `<d>` IEEE 754 double |
|
|
284
|
+
| 0x04 | str | `<I>` byte count + UTF-8 |
|
|
285
|
+
| 0x05 | bytes | `<I>` byte count + raw |
|
|
286
|
+
| 0x06 | list | `<I>` item count + items |
|
|
287
|
+
| 0x07 | tuple | `<I>` item count + items |
|
|
288
|
+
| 0x08 | dict | `<I>` pair count + key/value pairs |
|
|
289
|
+
| 0x09 | set | `<I>` item count + items (sorted) |
|
|
290
|
+
| 0x0A | ndarray | str(meta) + `<I>` data size + raw buffer |
|
|
291
|
+
| 0x0B | object | str(class_path) + encoded(state) |
|
|
292
|
+
| 0x0C | complex | `<dd>` real, imag |
|
|
293
|
+
| 0x0D | frozenset | `<I>` item count + items (sorted) |
|
|
294
|
+
| 0x0E | datetime | `<H>` str len + ISO 8601 string |
|
|
295
|
+
| 0x0F | date | `<HBB>` year, month, day |
|
|
296
|
+
| 0x10 | time | `<H>` str len + ISO 8601 string |
|
|
297
|
+
| 0x11 | timedelta (legacy) | `<iiI>` days, seconds, microseconds |
|
|
298
|
+
| 0x12 | Decimal | `<H>` str len + decimal string |
|
|
299
|
+
| 0x13 | Enum | str(class_path) + encoded(value) |
|
|
300
|
+
| 0x14 | bytearray | `<I>` byte count + raw |
|
|
301
|
+
| 0x15 | ref | `<I>` reference ID |
|
|
302
|
+
| 0x16 | UUID | 16 bytes raw |
|
|
303
|
+
| 0x17 | Path | `<I>` str len + UTF-8 path string |
|
|
304
|
+
| 0x18 | Tensor | str(meta) + `<I>` data size + raw buffer |
|
|
305
|
+
| 0x19 | timedelta2 | `<iiI>` days, seconds, microseconds |
|
|
306
|
+
|
|
307
|
+
**ndarray meta**: `"{dtype}|{shape}"` where shape is `"dim0xdim1x..."` (e.g., `"float32|100x100"`).
|
|
308
|
+
|
|
309
|
+
**Tensor meta**: `"{dtype}|{shape}|{requires_grad}"` (e.g., `"float32|256x256|0"`).
|
|
310
|
+
|
|
311
|
+
**Reference tracking**: Mutable containers (list, dict, set, etc.), strings, bytes, and arrays are assigned incrementing IDs. Tag 0x15 refers back to a previously seen object by ID, enabling circular reference support.
|
|
312
|
+
|
|
313
|
+
## License
|
|
314
|
+
|
|
315
|
+
MIT
|