A model file can influence a great deal of computation without being executable code.
That sounds obvious, but the distinction is often lost when a parser bug is reported. “Models are programs” has become a convenient summary of several different threat models. It is accurate for some formats, useful as a warning for many deployments, and badly misleading when applied to a data container whose only job is to describe tensors.
Executable serialization Tensor container
------------------------ ----------------
file chooses an operation file chooses a type
file resolves a callable file chooses a shape
file can invoke host code file chooses a byte range
The first needs an execution policy.
The second needs strict parsing and range checks.
I ran into that distinction while reporting an out-of-bounds read in MLX’s GGUF loader. The private security report was declined, with an invitation to file it publicly as a robustness issue. I did. Two days later a contributor’s fix, complete with regression tests and credit to the report, merged upstream.
The label changed. The unsafe read did not. The patch was still worth making.
The bug was ordinary parser work
MLX reads GGUF through a vendored C library. A tensor record contains an offset and enough metadata to determine the tensor’s byte length. The loader formed a pointer from the mapped file base plus the file-controlled offset, then later copied the declared number of bytes.
The missing invariant was simple:
tensor_offset + tensor_size <= mapped_file_size
Written safely, the check also avoids overflowing the addition:
// Pseudocode
if (tensor_offset > mapped_file_size ||
tensor_size > mapped_file_size - tensor_offset) {
return load_error;
}
Without that check, a crafted offset could point outside the mapping. In the tests, some values produced a fault and others wrapped back into mapped memory and returned the wrong bytes without an error.
There were no opcodes, imports, callbacks, or dynamic dispatch in the trigger. The file supplied a number. The parser trusted it. The number did not fit the buffer.
The merged fix added the bound at the MLX layer and covered both plain and quantized tensor paths. That is exactly what a careful binary parser should do.
Some model formats really are code
Python Pickle is the clearest counterexample. A Pickle stream is a small program for reconstructing objects. It can resolve globals and invoke callables during deserialization. Loading an arbitrary Pickle file is therefore much closer to running a script than to parsing an image.
This is why “only load files you trust” is the correct primary advice for legacy PyTorch packages that contain Pickle. A bounds check in one opcode handler cannot turn an executable serialization language into a safe data format.
The distinction is architectural, not semantic. A model may represent a neural network in both cases, but the container determines what powers the file receives while being loaded.
GGUF and SafeTensors are data containers
GGUF contains a header, typed metadata, tensor descriptions, and tensor bytes. SafeTensors contains a JSON header describing names, shapes, types, and byte ranges, followed by the tensor data. Neither format needs an instruction that says “call this function on the host.”
They can still be dangerous to parse. Lengths overflow, counts exceed arrays, offsets leave mappings, and error paths dereference null pointers. That is the ordinary risk of implementing a binary parser in systems code.
Calling such a file a script blurs the exact property that formats like SafeTensors were designed to provide: weights can be represented without inheriting Pickle’s code-execution behavior.
It also makes security reasoning harder. If every artifact that influences later computation is treated as executable by definition, then almost any input can be called a program. Images influence image-processing pipelines. Fonts influence layout engines. Media files control codecs. We do not use that fact to excuse memory corruption in their parsers.
Trust is not a parser invariant
Projects are allowed to define a narrow security boundary. A library can state that serialized artifacts must come from a trusted source and decline vulnerability reports that require a malicious file.
That policy does not make bounds checks unnecessary. Trust is a property of provenance and deployment. It is not something the parser can read from the file.
Modern model distribution makes this especially awkward. A base model may come from one publisher, a quantized copy from another, an adapter from a third, and a tokenizer from a fourth. CI systems mirror files. Registries cache them. Serving stacks load artifacts that were not produced by the team running the service.
No single step has to be reckless for the chain of custody to become unclear.
The cheapest defensive response is still to validate the arithmetic. Check that a range fits before using it. Reject an impossible shape. Cap a length before allocating. These checks help even when the project continues to classify malicious models as out of scope.
The MLX outcome is a useful model
The public path for the MLX report was practical.
- The project declined the private security classification and invited a public issue.
- The issue described the observed out-of-bounds read without arguing over labels.
- A contributor wrote a focused patch and regression tests, with clear credit to the report.
- The project merged the fix and closed the issue as completed.
No one had to agree that the bug deserved a CVE. They only had to agree that an offset from a file should not escape the mapping that contains it.
I find that outcome more encouraging than a long argument over labels. The public record is clear, the contributor received credit for the patch, and users get a safer loader.
That separation is healthy. Security policy decides what a project promises to defend. Parser hardening decides whether malformed data gets an error or unsafe behavior. The second decision can still be easy when the first is contested.
A model file is not automatically safe because it is data, and it is not automatically a script because it influences inference. The useful question is what powers the format gives the file while it is being loaded. The loader should grant no more than the format requires.
Public references: MLX issue #4136, merged fix #4179, and the adjacent metadata-bounds work in #4212.