Choosing a Safer Loading Boundary

Once a scanner’s coverage is known to be partial, the central security decision changes. The question is no longer whether one badge says “safe,” but where to place the boundary that limits what a model artifact can make the runtime do. This chapter compares four linked choices—format, loader, provenance, and containment—and shows when each is useful. The goal is a loading posture that matches the artifact’s compatibility needs and the consequences of failure, rather than a universal setting that pretends every model has the same trust requirements.

The strongest design usually reduces executable reconstruction before it reaches the runtime. When that is not possible, the next best move is to narrow the loader’s permitted behavior, make any exception explicit, and isolate the remaining risk. Provenance and scanning still matter, but they support these boundaries; they do not replace them.

What restricted loading changes

PyTorch offers a concrete example of tightening a deserialization boundary. Its documentation says that, starting in version 2.6, torch.load uses weights_only=True by default when the caller does not pass a custom pickle_module. The restricted unpickler is designed for ordinary tensor state dictionaries and selected primitive types. Unlike the default Python unpickler, it is not allowed to dynamically import anything during unpickling. That reduces the range of objects an artifact can ask the loader to reconstruct and narrows the remote-code-execution surface.

The setting is valuable because it changes the default question. Without a restriction, the loader is asked to interpret a broad Python object graph and decide whether the requested objects can be reconstructed. With weights_only=True, the loader begins from a smaller permitted set. A checkpoint that depends on a class or function outside that set does not quietly receive full authority; the load fails with an explanation of what the restricted unpickler could not accept. The failure turns a hidden trust decision into a visible one.

That boundary works best when the artifact follows the state-dict pattern. The publisher saves parameter and persistent-buffer values, while the consumer constructs the model architecture from code they control and applies the state to it. This separates learned data from the Python object that defines the model. It also makes compatibility more explicit: the consumer can see whether the state fits the expected architecture instead of asking a general deserializer to rebuild the whole module and its relationships.

Allowlisting is the compatibility valve for this design. An older or more specialized checkpoint may contain a tensor subclass or another object that the restricted unpickler does not recognize by default. PyTorch provides mechanisms to add selected functions or classes to a safe set, either persistently for the process or within a scoped context. That option is not a free pass. The documentation says to do it only when the source is trusted and the class or function has been checked. The consumer is deliberately taking responsibility for enlarging the loader’s permitted surface.

This is a better decision than silently falling back to unrestricted loading, but it still carries cost. A team must know what it is allowing, keep the relevant code and dependency versions under control, and understand whether the extra object is needed for inference or only for historical compatibility.