Path Escape: files that write themselves where they should not

Some model formats are really archives, and some let a tensor live in a separate file referenced by path. Abuse the path and a model can write over your SSH keys or read your secrets, all before any model code runs.

A lot of model formats are archives in a trench coat. A .keras file is a ZIP. A PyTorch checkpoint is a ZIP. Plenty of model bundles ship as TAR. And the thing about archives is that every entry inside carries a name, and that name is a path the extractor will write to.

Normally those names are tame: model/weights.bin, config.json, ordinary relative paths that land inside the directory you are unpacking into. But nothing forces them to be tame. An attacker can put a name like ../../../../home/you/.ssh/authorized_keys on an entry, and a careless extraction library will faithfully write that entry exactly there, outside the directory you thought you were unpacking into, over the top of a file that matters.

This is path traversal, CWE-22, and in the archive form it has been known long enough to have a nickname in the wider security world. It is dangerous in model land specifically because model archives are routinely unpacked by automated pipelines that nobody is watching, into environments that often have more access than they should. The payload is not code. It is a filename. And it does its damage during extraction, before a single line of model logic executes.

TAR brings an extra wrinkle: links. A TAR entry can be a symbolic or hard link whose target points outside the directory. Follow that link and subsequent writes get redirected to wherever the attacker aimed it. So checking the visible filenames is not enough. The link targets have to be checked too, because that is where this version of the trick hides.

The ONNX variant nobody talks about

There is a quieter cousin of this attack that is specific to ONNX, and it is one of the catches we are most proud of because it is so rarely discussed.

ONNX lets a model store its tensors in a separate file rather than inline, referenced by a location string inside the model. That is a reasonable feature for very large models. But the location string is just a path, and a malicious model can set it to a traversal path like ../../../../etc/passwd. When a runtime loads the model and resolves the external data, it follows that path to read, and in some pipelines write, a file well outside the model directory. So a model can be made to reach arbitrary files on the host purely through where it claims its weights live.

What makes this a clean signal is that there is no legitimate reason for it to look like an attack. A real external-data reference is always a simple relative filename sitting next to the model. A location string with ../ in it or an absolute path is not a grey area, it is a model trying to point outside its own directory, and there is no honest version of that.

Why this class is satisfying to defend

Most of this series is about judgement, about telling a benign call from a hostile one. Path escape is refreshingly black and white. An archive entry that escapes its directory and an external reference that points outside the model folder are wrong in a way that does not depend on context. That means this class can be caught early, decisively, and with almost no risk of false alarms, which is exactly how we treat it: the check runs first, before any heavier analysis, because there is no reason to do anything else with an archive that is already trying to escape.

What to do about it

If you write extraction code, never trust an entry name. Resolve every target path and confirm it stays inside the destination directory before you write a byte. Refuse links whose targets escape. This is a five-line guard that prevents a whole class of compromise, and an astonishing amount of production code still skips it.

If you consume models, prefer tooling that validates archive entries and external references before acting on them, and be aware that the danger here lands during unpacking, not during inference. By the time you are loading the model, the escape has already happened.