Extraction and path traversal¶
This module exists because of one job go/artifacts declined: opening the
archive.
That refusal was correct. A resolver that fetches and verifies has a clean boundary — bytes in, verified path out — and tar extraction is the single most reliably mishandled operation in that neighbourhood. Adding it would have put the estate's traversal risk inside the module every tool depends on.
The problem¶
A tar entry carries its own path, and that path is attacker-controlled in the
general case. An entry named ../../.ssh/authorized_keys joined onto an output
directory writes outside it. Extractors have shipped this defect for thirty
years, and the usual fix is a filter: reject names containing .., reject
absolute paths, canonicalise and compare prefixes.
Filters work until one does not. They accumulate cases — symlinks, hardlinks, absolute paths, unicode separators, platform-specific path rules — and each is a chance to have missed one.
What is done instead¶
The entry's path is never used to build an output path.
The destination is decided before the archive is opened: the caller's cache directory, plus a name this module chose. An entry is matched by comparing the base name of its path against the one library being looked for. Nothing else about the entry's name is read.
A hostile name can therefore cause a miss — the entry is not recognised as
the library and extraction reports ErrNotInArchive. It cannot cause a write to
an unintended location, because there is no code path in which the name reaches
a write.
That is the difference between a filter and a construction. A filter asks "is this name safe?" and can be wrong. This asks "is this the file I want?" and the answer changes nothing about where the bytes go.
Non-regular entries are skipped¶
Only regular files are considered. This is not a hypothetical: the real archives
contain symlinks beside the versioned object — libonnxruntime.so and
libonnxruntime.so.1 both point at it — so encountering one is the ordinary
case.
Were symlinks followed, an entry named like the library and pointing at
/etc/passwd would produce a "library" whose contents came from somewhere else
entirely.
This is not a defence against a hostile channel¶
Worth being precise, because a security note that overclaims gets trusted for things it does not cover.
By the time extraction runs, the archive's signature has been checked against a manifest naming the artefact and version, and its digest and length verified. The bytes are what the publisher signed. A hostile archive is not the threat model here.
The threat model is a careless extractor — which is a more likely problem than a compromised publisher, and one that a future edit to this file could reintroduce without any signature being broken.
That is why the guarantee is structural rather than a check somebody remembered to write, and why the test for it asserts the library landed inside the cache and that the entry's own path was not written. That test fails against a naive extractor, which is the only way to know it tests anything.
Bounds¶
The decompressed size is bounded at 512 MiB. The compressed length was already verified, but decompressed size is a different quantity, and a bound that is never hit costs nothing.