safeloadai 0.7.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.
- safeloadai-0.7.0/LICENSE +202 -0
- safeloadai-0.7.0/PKG-INFO +360 -0
- safeloadai-0.7.0/README.md +319 -0
- safeloadai-0.7.0/pyproject.toml +98 -0
- safeloadai-0.7.0/setup.cfg +4 -0
- safeloadai-0.7.0/src/safeloadai/__init__.py +11 -0
- safeloadai-0.7.0/src/safeloadai/__main__.py +340 -0
- safeloadai-0.7.0/src/safeloadai/api.py +276 -0
- safeloadai-0.7.0/src/safeloadai/behaviour.py +225 -0
- safeloadai-0.7.0/src/safeloadai/data/__init__.py +1 -0
- safeloadai-0.7.0/src/safeloadai/data/known_good.tsv +28 -0
- safeloadai-0.7.0/src/safeloadai/deep.py +153 -0
- safeloadai-0.7.0/src/safeloadai/detonate.py +377 -0
- safeloadai-0.7.0/src/safeloadai/detonate_keras.py +257 -0
- safeloadai-0.7.0/src/safeloadai/docker_sandbox.py +422 -0
- safeloadai-0.7.0/src/safeloadai/doctor.py +129 -0
- safeloadai-0.7.0/src/safeloadai/judge.py +158 -0
- safeloadai-0.7.0/src/safeloadai/known_good.py +56 -0
- safeloadai-0.7.0/src/safeloadai/options.py +225 -0
- safeloadai-0.7.0/src/safeloadai/override.py +158 -0
- safeloadai-0.7.0/src/safeloadai/parsing/__init__.py +0 -0
- safeloadai-0.7.0/src/safeloadai/parsing/decompress.py +163 -0
- safeloadai-0.7.0/src/safeloadai/parsing/detect.py +108 -0
- safeloadai-0.7.0/src/safeloadai/parsing/views.py +161 -0
- safeloadai-0.7.0/src/safeloadai/parsing/zipsafe.py +163 -0
- safeloadai-0.7.0/src/safeloadai/pipeline.py +294 -0
- safeloadai-0.7.0/src/safeloadai/profiles/__init__.py +203 -0
- safeloadai-0.7.0/src/safeloadai/profiles/registry.py +150 -0
- safeloadai-0.7.0/src/safeloadai/report.py +196 -0
- safeloadai-0.7.0/src/safeloadai/sandbox/__init__.py +7 -0
- safeloadai-0.7.0/src/safeloadai/sandbox/emulate.py +83 -0
- safeloadai-0.7.0/src/safeloadai/sandbox/guest/bootstrap.py +61 -0
- safeloadai-0.7.0/src/safeloadai/sandbox/guest/emulate.py +408 -0
- safeloadai-0.7.0/src/safeloadai/sandbox/guest/fakes.py +496 -0
- safeloadai-0.7.0/src/safeloadai/sandbox/guest/tasks.py +67 -0
- safeloadai-0.7.0/src/safeloadai/sandbox/host.py +257 -0
- safeloadai-0.7.0/src/safeloadai/sandbox/runner.py +204 -0
- safeloadai-0.7.0/src/safeloadai/sandbox/runtime.py +270 -0
- safeloadai-0.7.0/src/safeloadai/shared/__init__.py +0 -0
- safeloadai-0.7.0/src/safeloadai/shared/hdf5_meta.py +770 -0
- safeloadai-0.7.0/src/safeloadai/shared/joblib_layout.py +199 -0
- safeloadai-0.7.0/src/safeloadai/static/__init__.py +200 -0
- safeloadai-0.7.0/src/safeloadai/static/allowlists.py +569 -0
- safeloadai-0.7.0/src/safeloadai/static/clearance.py +253 -0
- safeloadai-0.7.0/src/safeloadai/static/h5_static.py +89 -0
- safeloadai-0.7.0/src/safeloadai/static/keras_static.py +519 -0
- safeloadai-0.7.0/src/safeloadai/static/lookalike.py +97 -0
- safeloadai-0.7.0/src/safeloadai/static/pickle_static.py +1031 -0
- safeloadai-0.7.0/src/safeloadai/static/pickle_vm.py +475 -0
- safeloadai-0.7.0/src/safeloadai/static/sources.py +737 -0
- safeloadai-0.7.0/src/safeloadai/static/torch_static.py +108 -0
- safeloadai-0.7.0/src/safeloadai/static/upstream/__init__.py +65 -0
- safeloadai-0.7.0/src/safeloadai/static/upstream/inert_modules.json +1 -0
- safeloadai-0.7.0/src/safeloadai/static/upstream/policies.json +1 -0
- safeloadai-0.7.0/src/safeloadai/static/upstream/torch_weights_only.py +152 -0
- safeloadai-0.7.0/src/safeloadai/strace_parse.py +181 -0
- safeloadai-0.7.0/src/safeloadai/verdict.py +113 -0
- safeloadai-0.7.0/src/safeloadai.egg-info/PKG-INFO +360 -0
- safeloadai-0.7.0/src/safeloadai.egg-info/SOURCES.txt +98 -0
- safeloadai-0.7.0/src/safeloadai.egg-info/dependency_links.txt +1 -0
- safeloadai-0.7.0/src/safeloadai.egg-info/entry_points.txt +2 -0
- safeloadai-0.7.0/src/safeloadai.egg-info/requires.txt +24 -0
- safeloadai-0.7.0/src/safeloadai.egg-info/top_level.txt +1 -0
- safeloadai-0.7.0/tests/test_allowlist_growth.py +264 -0
- safeloadai-0.7.0/tests/test_allowlist_review_20261019.py +322 -0
- safeloadai-0.7.0/tests/test_api.py +96 -0
- safeloadai-0.7.0/tests/test_audit_regressions.py +278 -0
- safeloadai-0.7.0/tests/test_automation.py +80 -0
- safeloadai-0.7.0/tests/test_clearance.py +214 -0
- safeloadai-0.7.0/tests/test_cli.py +51 -0
- safeloadai-0.7.0/tests/test_compiled_cache.py +189 -0
- safeloadai-0.7.0/tests/test_corpus.py +50 -0
- safeloadai-0.7.0/tests/test_dashboard.py +25 -0
- safeloadai-0.7.0/tests/test_dill.py +187 -0
- safeloadai-0.7.0/tests/test_embedded_keras.py +153 -0
- safeloadai-0.7.0/tests/test_emulation.py +225 -0
- safeloadai-0.7.0/tests/test_evaluation.py +332 -0
- safeloadai-0.7.0/tests/test_getattr.py +227 -0
- safeloadai-0.7.0/tests/test_h5.py +380 -0
- safeloadai-0.7.0/tests/test_host_guard.py +23 -0
- safeloadai-0.7.0/tests/test_joblib.py +288 -0
- safeloadai-0.7.0/tests/test_judge_and_deep.py +116 -0
- safeloadai-0.7.0/tests/test_known_good.py +104 -0
- safeloadai-0.7.0/tests/test_lookalike.py +122 -0
- safeloadai-0.7.0/tests/test_mutations.py +51 -0
- safeloadai-0.7.0/tests/test_options.py +190 -0
- safeloadai-0.7.0/tests/test_override.py +192 -0
- safeloadai-0.7.0/tests/test_parser.py +71 -0
- safeloadai-0.7.0/tests/test_polyglot.py +151 -0
- safeloadai-0.7.0/tests/test_profiles.py +283 -0
- safeloadai-0.7.0/tests/test_runtime_integrity.py +172 -0
- safeloadai-0.7.0/tests/test_sandbox.py +70 -0
- safeloadai-0.7.0/tests/test_sandbox_host_unit.py +32 -0
- safeloadai-0.7.0/tests/test_scan_wasi.py +94 -0
- safeloadai-0.7.0/tests/test_static_layer.py +321 -0
- safeloadai-0.7.0/tests/test_torch_weights_only.py +75 -0
- safeloadai-0.7.0/tests/test_trust_inert_from.py +152 -0
- safeloadai-0.7.0/tests/test_unsupported.py +92 -0
- safeloadai-0.7.0/tests/test_verdict_and_gate.py +128 -0
- safeloadai-0.7.0/tests/test_wasi_sandbox.py +127 -0
safeloadai-0.7.0/LICENSE
ADDED
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
|
|
2
|
+
Apache License
|
|
3
|
+
Version 2.0, January 2004
|
|
4
|
+
http://www.apache.org/licenses/
|
|
5
|
+
|
|
6
|
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
7
|
+
|
|
8
|
+
1. Definitions.
|
|
9
|
+
|
|
10
|
+
"License" shall mean the terms and conditions for use, reproduction,
|
|
11
|
+
and distribution as defined by Sections 1 through 9 of this document.
|
|
12
|
+
|
|
13
|
+
"Licensor" shall mean the copyright owner or entity authorized by
|
|
14
|
+
the copyright owner that is granting the License.
|
|
15
|
+
|
|
16
|
+
"Legal Entity" shall mean the union of the acting entity and all
|
|
17
|
+
other entities that control, are controlled by, or are under common
|
|
18
|
+
control with that entity. For the purposes of this definition,
|
|
19
|
+
"control" means (i) the power, direct or indirect, to cause the
|
|
20
|
+
direction or management of such entity, whether by contract or
|
|
21
|
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
22
|
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
23
|
+
|
|
24
|
+
"You" (or "Your") shall mean an individual or Legal Entity
|
|
25
|
+
exercising permissions granted by this License.
|
|
26
|
+
|
|
27
|
+
"Source" form shall mean the preferred form for making modifications,
|
|
28
|
+
including but not limited to software source code, documentation
|
|
29
|
+
source, and configuration files.
|
|
30
|
+
|
|
31
|
+
"Object" form shall mean any form resulting from mechanical
|
|
32
|
+
transformation or translation of a Source form, including but
|
|
33
|
+
not limited to compiled object code, generated documentation,
|
|
34
|
+
and conversions to other media types.
|
|
35
|
+
|
|
36
|
+
"Work" shall mean the work of authorship, whether in Source or
|
|
37
|
+
Object form, made available under the License, as indicated by a
|
|
38
|
+
copyright notice that is included in or attached to the work
|
|
39
|
+
(an example is provided in the Appendix below).
|
|
40
|
+
|
|
41
|
+
"Derivative Works" shall mean any work, whether in Source or Object
|
|
42
|
+
form, that is based on (or derived from) the Work and for which the
|
|
43
|
+
editorial revisions, annotations, elaborations, or other modifications
|
|
44
|
+
represent, as a whole, an original work of authorship. For the purposes
|
|
45
|
+
of this License, Derivative Works shall not include works that remain
|
|
46
|
+
separable from, or merely link (or bind by name) to the interfaces of,
|
|
47
|
+
the Work and Derivative Works thereof.
|
|
48
|
+
|
|
49
|
+
"Contribution" shall mean any work of authorship, including
|
|
50
|
+
the original version of the Work and any modifications or additions
|
|
51
|
+
to that Work or Derivative Works thereof, that is intentionally
|
|
52
|
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
53
|
+
or by an individual or Legal Entity authorized to submit on behalf of
|
|
54
|
+
the copyright owner. For the purposes of this definition, "submitted"
|
|
55
|
+
means any form of electronic, verbal, or written communication sent
|
|
56
|
+
to the Licensor or its representatives, including but not limited to
|
|
57
|
+
communication on electronic mailing lists, source code control systems,
|
|
58
|
+
and issue tracking systems that are managed by, or on behalf of, the
|
|
59
|
+
Licensor for the purpose of discussing and improving the Work, but
|
|
60
|
+
excluding communication that is conspicuously marked or otherwise
|
|
61
|
+
designated in writing by the copyright owner as "Not a Contribution."
|
|
62
|
+
|
|
63
|
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
64
|
+
on behalf of whom a Contribution has been received by Licensor and
|
|
65
|
+
subsequently incorporated within the Work.
|
|
66
|
+
|
|
67
|
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
68
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
69
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
70
|
+
copyright license to reproduce, prepare Derivative Works of,
|
|
71
|
+
publicly display, publicly perform, sublicense, and distribute the
|
|
72
|
+
Work and such Derivative Works in Source or Object form.
|
|
73
|
+
|
|
74
|
+
3. Grant of Patent License. Subject to the terms and conditions of
|
|
75
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
76
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
77
|
+
(except as stated in this section) patent license to make, have made,
|
|
78
|
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
79
|
+
where such license applies only to those patent claims licensable
|
|
80
|
+
by such Contributor that are necessarily infringed by their
|
|
81
|
+
Contribution(s) alone or by combination of their Contribution(s)
|
|
82
|
+
with the Work to which such Contribution(s) was submitted. If You
|
|
83
|
+
institute patent litigation against any entity (including a
|
|
84
|
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
85
|
+
or a Contribution incorporated within the Work constitutes direct
|
|
86
|
+
or contributory patent infringement, then any patent licenses
|
|
87
|
+
granted to You under this License for that Work shall terminate
|
|
88
|
+
as of the date such litigation is filed.
|
|
89
|
+
|
|
90
|
+
4. Redistribution. You may reproduce and distribute copies of the
|
|
91
|
+
Work or Derivative Works thereof in any medium, with or without
|
|
92
|
+
modifications, and in Source or Object form, provided that You
|
|
93
|
+
meet the following conditions:
|
|
94
|
+
|
|
95
|
+
(a) You must give any other recipients of the Work or
|
|
96
|
+
Derivative Works a copy of this License; and
|
|
97
|
+
|
|
98
|
+
(b) You must cause any modified files to carry prominent notices
|
|
99
|
+
stating that You changed the files; and
|
|
100
|
+
|
|
101
|
+
(c) You must retain, in the Source form of any Derivative Works
|
|
102
|
+
that You distribute, all copyright, patent, trademark, and
|
|
103
|
+
attribution notices from the Source form of the Work,
|
|
104
|
+
excluding those notices that do not pertain to any part of
|
|
105
|
+
the Derivative Works; and
|
|
106
|
+
|
|
107
|
+
(d) If the Work includes a "NOTICE" text file as part of its
|
|
108
|
+
distribution, then any Derivative Works that You distribute must
|
|
109
|
+
include a readable copy of the attribution notices contained
|
|
110
|
+
within such NOTICE file, excluding those notices that do not
|
|
111
|
+
pertain to any part of the Derivative Works, in at least one
|
|
112
|
+
of the following places: within a NOTICE text file distributed
|
|
113
|
+
as part of the Derivative Works; within the Source form or
|
|
114
|
+
documentation, if provided along with the Derivative Works; or,
|
|
115
|
+
within a display generated by the Derivative Works, if and
|
|
116
|
+
wherever such third-party notices normally appear. The contents
|
|
117
|
+
of the NOTICE file are for informational purposes only and
|
|
118
|
+
do not modify the License. You may add Your own attribution
|
|
119
|
+
notices within Derivative Works that You distribute, alongside
|
|
120
|
+
or as an addendum to the NOTICE text from the Work, provided
|
|
121
|
+
that such additional attribution notices cannot be construed
|
|
122
|
+
as modifying the License.
|
|
123
|
+
|
|
124
|
+
You may add Your own copyright statement to Your modifications and
|
|
125
|
+
may provide additional or different license terms and conditions
|
|
126
|
+
for use, reproduction, or distribution of Your modifications, or
|
|
127
|
+
for any such Derivative Works as a whole, provided Your use,
|
|
128
|
+
reproduction, and distribution of the Work otherwise complies with
|
|
129
|
+
the conditions stated in this License.
|
|
130
|
+
|
|
131
|
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
132
|
+
any Contribution intentionally submitted for inclusion in the Work
|
|
133
|
+
by You to the Licensor shall be under the terms and conditions of
|
|
134
|
+
this License, without any additional terms or conditions.
|
|
135
|
+
Notwithstanding the above, nothing herein shall supersede or modify
|
|
136
|
+
the terms of any separate license agreement you may have executed
|
|
137
|
+
with Licensor regarding such Contributions.
|
|
138
|
+
|
|
139
|
+
6. Trademarks. This License does not grant permission to use the trade
|
|
140
|
+
names, trademarks, service marks, or product names of the Licensor,
|
|
141
|
+
except as required for reasonable and customary use in describing the
|
|
142
|
+
origin of the Work and reproducing the content of the NOTICE file.
|
|
143
|
+
|
|
144
|
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
145
|
+
agreed to in writing, Licensor provides the Work (and each
|
|
146
|
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
147
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
148
|
+
implied, including, without limitation, any warranties or conditions
|
|
149
|
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
150
|
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
151
|
+
appropriateness of using or redistributing the Work and assume any
|
|
152
|
+
risks associated with Your exercise of permissions under this License.
|
|
153
|
+
|
|
154
|
+
8. Limitation of Liability. In no event and under no legal theory,
|
|
155
|
+
whether in tort (including negligence), contract, or otherwise,
|
|
156
|
+
unless required by applicable law (such as deliberate and grossly
|
|
157
|
+
negligent acts) or agreed to in writing, shall any Contributor be
|
|
158
|
+
liable to You for damages, including any direct, indirect, special,
|
|
159
|
+
incidental, or consequential damages of any character arising as a
|
|
160
|
+
result of this License or out of the use or inability to use the
|
|
161
|
+
Work (including but not limited to damages for loss of goodwill,
|
|
162
|
+
work stoppage, computer failure or malfunction, or any and all
|
|
163
|
+
other commercial damages or losses), even if such Contributor
|
|
164
|
+
has been advised of the possibility of such damages.
|
|
165
|
+
|
|
166
|
+
9. Accepting Warranty or Additional Liability. While redistributing
|
|
167
|
+
the Work or Derivative Works thereof, You may choose to offer,
|
|
168
|
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
169
|
+
or other liability obligations and/or rights consistent with this
|
|
170
|
+
License. However, in accepting such obligations, You may act only
|
|
171
|
+
on Your own behalf and on Your sole responsibility, not on behalf
|
|
172
|
+
of any other Contributor, and only if You agree to indemnify,
|
|
173
|
+
defend, and hold each Contributor harmless for any liability
|
|
174
|
+
incurred by, or claims asserted against, such Contributor by reason
|
|
175
|
+
of your accepting any such warranty or additional liability.
|
|
176
|
+
|
|
177
|
+
END OF TERMS AND CONDITIONS
|
|
178
|
+
|
|
179
|
+
APPENDIX: How to apply the Apache License to your work.
|
|
180
|
+
|
|
181
|
+
To apply the Apache License to your work, attach the following
|
|
182
|
+
boilerplate notice, with the fields enclosed by brackets "[]"
|
|
183
|
+
replaced with your own identifying information. (Don't include
|
|
184
|
+
the brackets!) The text should be enclosed in the appropriate
|
|
185
|
+
comment syntax for the file format. We also recommend that a
|
|
186
|
+
file or class name and description of purpose be included on the
|
|
187
|
+
same "printed page" as the copyright notice for easier
|
|
188
|
+
identification within third-party archives.
|
|
189
|
+
|
|
190
|
+
Copyright 2026 Bartosz Mosionek
|
|
191
|
+
|
|
192
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
193
|
+
you may not use this file except in compliance with the License.
|
|
194
|
+
You may obtain a copy of the License at
|
|
195
|
+
|
|
196
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
197
|
+
|
|
198
|
+
Unless required by applicable law or agreed to in writing, software
|
|
199
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
200
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
201
|
+
See the License for the specific language governing permissions and
|
|
202
|
+
limitations under the License.
|
|
@@ -0,0 +1,360 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: safeloadai
|
|
3
|
+
Version: 0.7.0
|
|
4
|
+
Summary: SafeLoadAI: decide whether an ML model file is safe to load (static analysis + sandboxed emulation).
|
|
5
|
+
Author: Bartosz Mosionek
|
|
6
|
+
License-Expression: Apache-2.0
|
|
7
|
+
Project-URL: Homepage, https://github.com/bartekmosionek12-bit/Sandbox
|
|
8
|
+
Classifier: Development Status :: 4 - Beta
|
|
9
|
+
Classifier: Intended Audience :: Developers
|
|
10
|
+
Classifier: Intended Audience :: Science/Research
|
|
11
|
+
Classifier: Topic :: Security
|
|
12
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
17
|
+
Classifier: Operating System :: OS Independent
|
|
18
|
+
Requires-Python: >=3.11
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
License-File: LICENSE
|
|
21
|
+
Requires-Dist: safeloadai-runtime==0.1.0
|
|
22
|
+
Requires-Dist: wasmtime<50,>=49.0.0
|
|
23
|
+
Provides-Extra: lz4
|
|
24
|
+
Requires-Dist: lz4<5,>=4.0; extra == "lz4"
|
|
25
|
+
Provides-Extra: judge
|
|
26
|
+
Requires-Dist: anthropic<1.0,>=0.125; extra == "judge"
|
|
27
|
+
Provides-Extra: dashboard
|
|
28
|
+
Requires-Dist: Flask<4.0,>=3.0; extra == "dashboard"
|
|
29
|
+
Provides-Extra: test
|
|
30
|
+
Requires-Dist: pytest; extra == "test"
|
|
31
|
+
Requires-Dist: Flask<4.0,>=3.0; extra == "test"
|
|
32
|
+
Requires-Dist: numpy; extra == "test"
|
|
33
|
+
Requires-Dist: joblib; extra == "test"
|
|
34
|
+
Requires-Dist: scikit-learn; extra == "test"
|
|
35
|
+
Requires-Dist: lz4<5,>=4.0; extra == "test"
|
|
36
|
+
Provides-Extra: dev
|
|
37
|
+
Requires-Dist: safeloadai[test]; extra == "dev"
|
|
38
|
+
Requires-Dist: ruff; extra == "dev"
|
|
39
|
+
Requires-Dist: build; extra == "dev"
|
|
40
|
+
Dynamic: license-file
|
|
41
|
+
|
|
42
|
+
<!-- SPDX-License-Identifier: Apache-2.0 -->
|
|
43
|
+
# safeloadai
|
|
44
|
+
|
|
45
|
+
**safeloadai tells you whether a model file is safe to load before you load it.**
|
|
46
|
+
It reads pickle, joblib, PyTorch, Keras and legacy Keras HDF5 files without running
|
|
47
|
+
them, and runs whatever code they would run inside a WebAssembly sandbox with fake
|
|
48
|
+
APIs. It is beta software (0.7.0): see [LIMITATIONS](#limitations) before relying on it.
|
|
49
|
+
|
|
50
|
+
## Quick start
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
pip install safeloadai # Python 3.11+, Linux, macOS or Windows; nothing is downloaded at run time
|
|
54
|
+
safeloadai scan model.pkl # exit 0 CLEAN, 1 MALICIOUS, 3 SUSPICIOUS, 5 UNSUPPORTED
|
|
55
|
+
safeloadai scan models/ --json # a whole directory, JSON reports
|
|
56
|
+
safeloadai doctor # checks the sandbox runtime and runs it once
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
```python
|
|
60
|
+
from safeloadai import scan, safe_load, UnsafeModelError
|
|
61
|
+
|
|
62
|
+
report = scan("model.pkl") # never loads the file on this machine
|
|
63
|
+
print(report.verdict) # CLEAN, SUSPICIOUS, MALICIOUS or UNSUPPORTED
|
|
64
|
+
print(report.reasons) # why, in plain words
|
|
65
|
+
|
|
66
|
+
model = safe_load("model.pkl") # loads only a CLEAN file, else raises UnsafeModelError
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Supported formats: pickle (`.pkl`, `.pickle`), joblib (uncompressed, zlib, gzip, bz2,
|
|
70
|
+
lzma, xz, lz4), PyTorch (`.pt`, `.pth`, `.bin`, zip and legacy), Keras (`.keras`) and
|
|
71
|
+
legacy Keras HDF5 (`.h5`, read without h5py or libhdf5). Anything else is UNSUPPORTED,
|
|
72
|
+
never CLEAN.
|
|
73
|
+
|
|
74
|
+
The defaults are the strictest settings. Every option that loosens a verdict is off
|
|
75
|
+
by default, is announced on the command line, is recorded in `Report.options`, and is
|
|
76
|
+
documented with its risk in [docs/OPTIONS.md](docs/OPTIONS.md).
|
|
77
|
+
|
|
78
|
+
### Install
|
|
79
|
+
|
|
80
|
+
`pip install safeloadai` also installs `safeloadai-runtime`, the pinned CPython-on-WASI
|
|
81
|
+
runtime. Nothing is downloaded at run time. Compiling that runtime takes a few seconds,
|
|
82
|
+
so the compiled module is cached in your per-user cache directory:
|
|
83
|
+
|
|
84
|
+
| platform | cache directory | fallbacks, in order |
|
|
85
|
+
|---|---|---|
|
|
86
|
+
| Windows | `%LOCALAPPDATA%\safeloadai` | `%APPDATA%\safeloadai`, `%TEMP%\safeloadai-<user>` |
|
|
87
|
+
| macOS | `~/Library/Caches/safeloadai` | none |
|
|
88
|
+
| Linux and others | `$XDG_CACHE_HOME/safeloadai` | `~/.cache/safeloadai` (never a shared `/tmp`) |
|
|
89
|
+
|
|
90
|
+
`SAFELOADAI_HOME` overrides all of them. If no location can be used, the runtime is
|
|
91
|
+
compiled in memory for every scan: slower, with the same verdicts. `safeloadai doctor`
|
|
92
|
+
shows the location in use, or each location it tried with the error it got. Details:
|
|
93
|
+
[docs/OPTIONS.md](docs/OPTIONS.md#compiled-module-cache).
|
|
94
|
+
|
|
95
|
+
## How it works
|
|
96
|
+
|
|
97
|
+
```
|
|
98
|
+
model file
|
|
99
|
+
|
|
|
100
|
+
every reading a loader could make
|
|
101
|
+
(pickle at 0, joblib, ZIP from the end, HDF5)
|
|
102
|
+
|
|
|
103
|
+
+-----------+------------+
|
|
104
|
+
| |
|
|
105
|
+
static analysis emulation in a WASI sandbox
|
|
106
|
+
(opcodes, imports, (CPython 3.12 in WebAssembly,
|
|
107
|
+
Keras config, fake os/subprocess/socket/torch...,
|
|
108
|
+
exact allowlist) no network, no files, CPU/memory caps)
|
|
109
|
+
| |
|
|
110
|
+
+-----------+------------+
|
|
111
|
+
|
|
|
112
|
+
worst verdict wins: CLEAN / SUSPICIOUS / MALICIOUS / UNSUPPORTED
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
1. **Every reading.** A file can be a pickle for `pickle.load` and a ZIP archive for
|
|
116
|
+
`zipfile` at once (a polyglot). Each reading is analysed on its own, and any
|
|
117
|
+
ambiguity is at least SUSPICIOUS.
|
|
118
|
+
2. **Static analysis.** Every pickle opcode is walked. Imports must match an exact
|
|
119
|
+
`(module, qualname)` allowlist; a known sink (`os.system`, `eval`, `read_pickle`, ...)
|
|
120
|
+
that is called is MALICIOUS. Keras configs need exact module and class names.
|
|
121
|
+
3. **Emulation.** The file is unpickled (and Keras Lambda layers are called) inside
|
|
122
|
+
CPython compiled to WebAssembly, under wasmtime, with recording fake modules. Process,
|
|
123
|
+
network, file-write or code-execution calls are MALICIOUS.
|
|
124
|
+
4. **What may pass outside the allowlist** (0.7.0): a global that is only stored and
|
|
125
|
+
never called; classes the pinned library source shows are state-only; what torch's
|
|
126
|
+
`weights_only=True` loader accepts; profiles of well-known files (Trainer files,
|
|
127
|
+
Ultralytics checkpoints); and a shipped list of reviewed files matched by exact
|
|
128
|
+
sha256. Each only *allows*: none of them lowers a MALICIOUS or RISKY finding, and
|
|
129
|
+
`Report.cleared` says which mechanism accepted what.
|
|
130
|
+
|
|
131
|
+
`safe_load` loads the same private copy it scanned, with a restricted unpickler,
|
|
132
|
+
`torch.load(weights_only=True)` or Keras `safe_mode=True`.
|
|
133
|
+
Details: [docs/HOW_IT_WORKS.md](docs/HOW_IT_WORKS.md), [docs/PROFILES.md](docs/PROFILES.md),
|
|
134
|
+
design decisions in [DECISIONS.md](DECISIONS.md).
|
|
135
|
+
|
|
136
|
+
### Verdicts and the informed override
|
|
137
|
+
|
|
138
|
+
Every reason has a category:
|
|
139
|
+
|
|
140
|
+
- **MALICIOUS:** evidence of a known sink (process, network, file write, code
|
|
141
|
+
execution) or of tampering with the sandbox's evidence.
|
|
142
|
+
- **RISKY:** a construct the scanner refuses because it can be abused: code-carrying
|
|
143
|
+
layers, polyglots, parse errors, limits, known gadgets, a module name that imitates
|
|
144
|
+
an allowlisted one (`kerasx`, `sklearn2`, `kеras` with a Cyrillic `е`).
|
|
145
|
+
- **UNVERIFIABLE:** a named object whose code the scanner cannot see, such as a class
|
|
146
|
+
outside the allowlist or a custom Keras layer. Nothing in it is known to be dangerous.
|
|
147
|
+
|
|
148
|
+
A file whose **every** reason is UNVERIFIABLE (a *soft block*) can be accepted item by
|
|
149
|
+
item, for that one file, and the acceptance is recorded in the Report. One RISKY or
|
|
150
|
+
MALICIOUS reason (a *hard block*) makes an override impossible, and there is no switch
|
|
151
|
+
that accepts everything.
|
|
152
|
+
|
|
153
|
+
```python
|
|
154
|
+
model = safe_load("model.pkl", accept_unverified=["mypkg.models.Thing"]) # this file only
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
```bash
|
|
158
|
+
safeloadai scan convnext.h5 --accept-unverified LayerScale # name every item
|
|
159
|
+
safeloadai scan model.pkl --trust-inert-from scikit-learn # judge your installed sklearn's classes
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
Accepting an UNVERIFIABLE item means trusting that class's code; see
|
|
163
|
+
[docs/OPTIONS.md](docs/OPTIONS.md#accept_unverified).
|
|
164
|
+
|
|
165
|
+
## Results
|
|
166
|
+
|
|
167
|
+
Run of 2026-10-20, safeloadai 0.7.0, in a disposable cloud container with
|
|
168
|
+
`evaluation/guard.py` on and no `--deep`. Full tables:
|
|
169
|
+
[eval-v070](evaluation/results/eval-v070-20261020T063605Z.md).
|
|
170
|
+
|
|
171
|
+
**Detection.** "Detected" means any verdict other than CLEAN. safeloadai's verdict was
|
|
172
|
+
MALICIOUS on every one of these by default.
|
|
173
|
+
|
|
174
|
+
| set | safeloadai default | real opt-in | upper bound | picklescan 1.0.5 | fickling 0.1.12 | modelscan 0.8.8 |
|
|
175
|
+
|---|---|---|---|---|---|---|
|
|
176
|
+
| lab marker payloads (incl. 6 polyglots) | 59 / 59 | 59 / 59 | 56 / 59 | 35 / 59 | 40 / 59 | 24 / 59 |
|
|
177
|
+
| mutations (`tests/corpus/mutate.py`) | 774 / 774 | 774 / 774 | 774 / 774 | 678 / 774 | 666 / 774 | 618 / 774 |
|
|
178
|
+
| real Hub files flagged by the Hub's scanners, labelled malicious from that evidence | 49 / 49 | 49 / 49 | 49 / 49 | 31 / 49 | 32 / 49 | 29 / 49 |
|
|
179
|
+
|
|
180
|
+
**Benign Hugging Face files, held-out half** (501 files from repos no review looked at;
|
|
181
|
+
the tune half, 432 files, is what lists, policies and profiles were built from). No
|
|
182
|
+
benign file is MALICIOUS in any configuration.
|
|
183
|
+
|
|
184
|
+
| configuration | CLEAN | soft block (UNVERIFIABLE only) | hard block (RISKY) | UNSUPPORTED |
|
|
185
|
+
|---|---|---|---|---|
|
|
186
|
+
| 0.5.0 default | 352 | 101 | 48 (33 of them not a readable format) | - |
|
|
187
|
+
| **0.7.0 default** | **372** | **81** | **15** | **33** |
|
|
188
|
+
| 0.7.0 real opt-in: `--trust-inert-from scikit-learn transformers torch` | 372 | 81 | 15 | 33 |
|
|
189
|
+
| 0.7.0 upper bound: accept all unverifiable (not recommended) | 453 | 0 | 15 | 33 |
|
|
190
|
+
|
|
191
|
+
- **Default.** 129 of 501 held-out files are not CLEAN (0.5.0: 149). Of the
|
|
192
|
+
468 files in a format safeloadai reads, 96 are blocked (0.5.0: 116), and
|
|
193
|
+
81 of those are soft blocks a user can accept item by item.
|
|
194
|
+
- **Real opt-in** reads the installed source of the named libraries instead of the
|
|
195
|
+
pinned one. Run against the same versions as the pinned policies it changes nothing,
|
|
196
|
+
as it should; it helps when your installed version differs from the pinned one.
|
|
197
|
+
- **Upper bound** accepts every UNVERIFIABLE item without reading it. It also lets 3
|
|
198
|
+
lab payloads through (an unknown pickle global, a custom Keras object, a custom
|
|
199
|
+
object in an `.h5`), which is why no switch does this.
|
|
200
|
+
- **The known-file list** clears 27 tune files and, by construction, no held-out file.
|
|
201
|
+
- **UNSUPPORTED** files are OpenVINO-style `.bin` weights and similar: no pickle,
|
|
202
|
+
archive or HDF5 reading exists, so safeloadai says nothing about them.
|
|
203
|
+
- **Other scanners** flag 4 (picklescan), 4 (modelscan) and 183 (fickling) of the 501
|
|
204
|
+
held-out files, but miss many of the malicious files above.
|
|
205
|
+
|
|
206
|
+
What still blocks held-out files most often: xgboost `Booster` (19 files, kept out by
|
|
207
|
+
an owner decision), the authors' own Keras losses and layers, newer Ultralytics heads
|
|
208
|
+
(`Segment26`), Keras/torch optimizers and `__main__` classes. The full Pareto table is in
|
|
209
|
+
the evaluation report.
|
|
210
|
+
|
|
211
|
+
**How to read these numbers.** The malicious Hub files are a convenience sample (test
|
|
212
|
+
payloads and proofs of concept, mostly simple pickles); they show that what the Hub's
|
|
213
|
+
scanners flag is caught, not how safeloadai does against a targeted attacker. The lab
|
|
214
|
+
and mutation payloads are marker rebuilds, and the mutation generator was written with
|
|
215
|
+
knowledge of the scanner.
|
|
216
|
+
|
|
217
|
+
### Compared with published work
|
|
218
|
+
|
|
219
|
+
PickleBall ([arXiv 2508.15987](https://arxiv.org/abs/2508.15987)) also
|
|
220
|
+
derives policies from library source code, and enforces them in a loader. It reports 0 %
|
|
221
|
+
false negatives and a 20.2 % false-positive rate (benign models it refuses to load) on
|
|
222
|
+
its own dataset of pickle-only models. safeloadai's state-only policies follow the same
|
|
223
|
+
idea at development time. The numbers are not directly comparable: the datasets differ
|
|
224
|
+
(this corpus is stratified across pickle, joblib, PyTorch, Keras and HDF5, and includes
|
|
225
|
+
files in formats no tool reads), and the counting differs (safeloadai counts every
|
|
226
|
+
non-CLEAN benign file, soft blocks included).
|
|
227
|
+
|
|
228
|
+
## LIMITATIONS
|
|
229
|
+
|
|
230
|
+
- **Beta.** The allowlist, policies and profiles cover the library versions and files
|
|
231
|
+
the tune half of the corpus needed. Benign files from other libraries or versions will
|
|
232
|
+
often be SUSPICIOUS (UNVERIFIABLE).
|
|
233
|
+
- **Emulation is not execution.** The fake APIs return plausible values, not
|
|
234
|
+
real ones. A payload whose behaviour depends on real results, real files or
|
|
235
|
+
real time may take a different path in the sandbox.
|
|
236
|
+
- **Covered:** introspection (`__subclasses__`, `__globals__`, `type`,
|
|
237
|
+
`_getframe`, dunder `getattr`), sleeping, reading tensor values and
|
|
238
|
+
unknown globals are recorded as anomalies and make the file SUSPICIOUS.
|
|
239
|
+
- **Not covered:** other ways of detecting the emulator are not.
|
|
240
|
+
- **The Lambda rule.** A Keras Lambda layer is CLEAN only if static analysis
|
|
241
|
+
succeeds, emulation completes, and neither records a dangerous behaviour or an
|
|
242
|
+
anomaly. The Python version that wrote the bytecode is never guessed, because Keras
|
|
243
|
+
files do not record it. One case stays undetectable: bytecode from another CPython
|
|
244
|
+
version that happens to be byte-identical to valid 3.12 bytecode (for example
|
|
245
|
+
`lambda x: x` from 3.11) passes every check and is emulated as 3.12 bytecode.
|
|
246
|
+
- **Evidence channel.** The payload runs in the same guest interpreter as the
|
|
247
|
+
recorder. It can find the nonce and append well-formed records of its own, but it
|
|
248
|
+
cannot remove records the host has already received, so it can add noise but cannot
|
|
249
|
+
turn a recorded alarm into a clean log. Writes without the nonce count as tampering
|
|
250
|
+
and are MALICIOUS.
|
|
251
|
+
- **An override on UNVERIFIABLE means trusting that class's code.** Accepting it tells
|
|
252
|
+
`safe_load` to import and run whatever the named module does, with your privileges.
|
|
253
|
+
Accept only items whose source you know and trust. Look-alike names of allowlisted
|
|
254
|
+
modules are RISKY instead, but a name that is merely unfamiliar
|
|
255
|
+
(`mycompany.layers.Foo`) can be accepted, and so can a payload hidden behind it.
|
|
256
|
+
- **Source-derived policies trust the pinned library versions.** A state-only class
|
|
257
|
+
is judged from the source of the version listed in `static/upstream/REVIEW.md`. If a
|
|
258
|
+
newer version adds a `__setstate__` that does something dangerous, the pinned policy
|
|
259
|
+
does not know. `--trust-inert-from DIST` judges the installed version instead, and so
|
|
260
|
+
trusts that installation (see [docs/OPTIONS.md](docs/OPTIONS.md#trust_inert_from)).
|
|
261
|
+
- **Profiles use the file name and the producer's imports.** A file that matches both
|
|
262
|
+
but not the structure gets a RISKY reason; a payload in a file outside any profile is
|
|
263
|
+
judged by the usual rules. A profile never accepts a sink.
|
|
264
|
+
- **Known files help only byte-identical files.** The shipped list holds 27 reviewed
|
|
265
|
+
files from the tune half of the corpus; a file that differs by one byte gets no
|
|
266
|
+
benefit. `--no-known-good` turns the list off.
|
|
267
|
+
- **Allowlist false positives.**
|
|
268
|
+
- **numpy random state:** `__generator_ctor`, `__randomstate_ctor` and
|
|
269
|
+
`__bit_generator_ctor` call one of their arguments, so they are accepted
|
|
270
|
+
only with the exact arguments numpy itself writes.
|
|
271
|
+
- **TorchScript** is always SUSPICIOUS.
|
|
272
|
+
- **xgboost `Booster`** stays UNVERIFIABLE (it hands raw bytes to a native parser);
|
|
273
|
+
save with `save_model("model.json")` instead.
|
|
274
|
+
- **UNSUPPORTED formats.** OpenVINO and CTranslate2 weights and any other format with no pickle, joblib, ZIP or HDF5 reading are UNSUPPORTED. safeloadai
|
|
275
|
+
says nothing about them, and `safe_load` never loads them.
|
|
276
|
+
- **joblib.** Supported: joblib 0.10 and newer, one stream of zlib, gzip, bz2, lzma,
|
|
277
|
+
xz or lz4 (lz4 needs `pip install "safeloadai[lz4]"`). SUSPICIOUS by design: the
|
|
278
|
+
multi-file format of joblib < 0.10, structured dtypes holding Python objects, a
|
|
279
|
+
wrapper built another way than joblib's own, array data inside a pickle frame, and
|
|
280
|
+
decompressed output above 1 GiB or a ratio above 1000:1. The check of what a plain
|
|
281
|
+
`pickle.load` would import from joblib's array bytes stops at the first import whose
|
|
282
|
+
top-level module no environment could provide.
|
|
283
|
+
- **Legacy HDF5.** Supported: superblock versions 0 to 3, old-style and compact groups,
|
|
284
|
+
fixed and variable-length string attributes. Weight datasets are never read, so bugs
|
|
285
|
+
in libhdf5 or h5py weight parsing are out of scope. SUSPICIOUS by design: external
|
|
286
|
+
links or data files, virtual datasets, driver info, a user block, dense storage, odd
|
|
287
|
+
root attributes, more than 100,000 objects and anything the reader cannot parse.
|
|
288
|
+
Keras 2 Lambda layers are rarely CLEAN.
|
|
289
|
+
- **Polyglot files.** Recognised readings: a pickle at offset 0, a joblib-compressed
|
|
290
|
+
stream at offset 0, a ZIP archive found from its end, and the first HDF5 superblock at
|
|
291
|
+
0, 512, 1024, ... A reading the scanner does not know (another archive format, data a
|
|
292
|
+
custom loader reads at another offset) is not analysed.
|
|
293
|
+
- **CRC-32.** A PyTorch member with a bad CRC-32 is analysed anyway and the file is
|
|
294
|
+
RISKY; a `.keras` member with a bad CRC-32 is refused, as `zipfile` refuses it.
|
|
295
|
+
- **CLEAN Lambda models do not load through `safe_load`,** because Keras safe mode
|
|
296
|
+
refuses Lambda bytecode. Legacy `.h5` files are never loaded by `safe_load`.
|
|
297
|
+
- **Loading a CLEAN pickle imports allowlisted libraries on your machine,** such as
|
|
298
|
+
numpy and scikit-learn, and runs their reconstruction code. You are trusting them.
|
|
299
|
+
- **The WASI sandbox is only as strong as wasmtime** and a third-party CPython WASI
|
|
300
|
+
build (pinned by sha256). Fuel bounds CPU and the host kills the process at the
|
|
301
|
+
wall-clock limit; large or slow models can hit the limits and are then SUSPICIOUS.
|
|
302
|
+
Files up to 4 GiB are read into memory on the host.
|
|
303
|
+
- **Deep mode (`--deep`, off) runs the real loader on the real kernel** in Docker,
|
|
304
|
+
bubblewrap or nsjail. Docker shares the kernel; the nsjail flags have not been
|
|
305
|
+
tested; a payload can print fake events, which can only raise the verdict. The Docker
|
|
306
|
+
backend needs a source checkout.
|
|
307
|
+
- **The runtime package is trusted at install time.** safeloadai checks the `.wasm`
|
|
308
|
+
hash before every use, but pip runs whatever a package does when it is installed.
|
|
309
|
+
Install `safeloadai-runtime` only from PyPI under that exact name and version.
|
|
310
|
+
- **The judge (`--judge`, off) only adds cost and escalation.** It sees no free text
|
|
311
|
+
and can never lower a verdict.
|
|
312
|
+
|
|
313
|
+
## Reporting a bypass
|
|
314
|
+
|
|
315
|
+
A file that safeloadai calls CLEAN but that runs code when loaded is a security bug.
|
|
316
|
+
Report it privately through the repository's **Security** tab ("Report a
|
|
317
|
+
vulnerability"), with a harmless payload. Details and scope: [SECURITY.md](SECURITY.md).
|
|
318
|
+
False positives are welcome as normal issues.
|
|
319
|
+
|
|
320
|
+
## Development
|
|
321
|
+
|
|
322
|
+
```bash
|
|
323
|
+
python packages/safeloadai-runtime/fetch_runtime.py # once: download + verify the pinned runtime
|
|
324
|
+
pip install ./packages/safeloadai-runtime
|
|
325
|
+
pip install -e ".[dev]"
|
|
326
|
+
ruff check .
|
|
327
|
+
python -m pytest -q
|
|
328
|
+
python -m tests.corpus.build_corpus /tmp/corpus
|
|
329
|
+
python evaluation/run_eval.py --lab /tmp/corpus --hf /path/to/hf_corpus # --help for every input
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
- **Safe tests:** a host guard refuses process execution (except the sandbox runner),
|
|
333
|
+
non-loopback network connections and unrestricted unpickling; sample payloads are
|
|
334
|
+
markers only (`echo MARKER`, `/tmp/safeloadai_marker`, 127.0.0.1).
|
|
335
|
+
- **CI** builds both wheels, installs them into a clean venv, blocks the network and
|
|
336
|
+
runs the whole suite against the installed wheels on Linux, macOS and Windows with
|
|
337
|
+
Python 3.11 to 3.13. A skipped sandbox test is an error there.
|
|
338
|
+
- **Update monitoring detects and never auto-trusts:** Dependabot, a weekly
|
|
339
|
+
`upstream-drift` job that scans models written by the latest libraries and opens an
|
|
340
|
+
issue on drift, and `runtime-watch` for new WASI CPython and wasmtime releases
|
|
341
|
+
([docs/UPDATING_RUNTIME.md](docs/UPDATING_RUNTIME.md)).
|
|
342
|
+
- **Releasing:** `.github/workflows/publish.yml` uploads both packages with PyPI
|
|
343
|
+
Trusted Publishing, started by hand, to testpypi or pypi. Changes are in
|
|
344
|
+
[CHANGELOG.md](CHANGELOG.md).
|
|
345
|
+
|
|
346
|
+
The report viewer runs with `flask --app app.server run --host 127.0.0.1`; it renders
|
|
347
|
+
Report JSON and does not scan files. The audit that started this rewrite is in
|
|
348
|
+
[AUDIT.md](AUDIT.md).
|
|
349
|
+
|
|
350
|
+
## Statement on AI use
|
|
351
|
+
|
|
352
|
+
This project was written by Bartosz Mosionek together with an LLM assistant (Claude):
|
|
353
|
+
code, tests, reviews of library source and documentation. The optional judge uses the
|
|
354
|
+
Anthropic API. The author understands the code and takes technical responsibility for
|
|
355
|
+
it.
|
|
356
|
+
|
|
357
|
+
## License
|
|
358
|
+
|
|
359
|
+
Apache-2.0, see [LICENSE](LICENSE). The `safeloadai-runtime` package also ships CPython
|
|
360
|
+
built for WASI under its own licences (`packages/safeloadai-runtime/NOTICE`).
|