Finding
ZipArchive::extract() sanitizes entry paths before writing (the zip crate strips leading slashes and resolves ..). The inventory loop that runs afterward does not apply equivalent sanitization: it joins the raw entry.name() to output_dir. For any entry whose name contains path-unsafe components, the recorded ExtractedFile.path will not match the actual filesystem location of the written file. The extraction pass and the inventory pass are independent and can disagree on the resulting path.
Evidence
crates/ergasia/src/extract/zip_extract.rs:27:
archive.extract(output_dir)
uses zip's sanitizing extractor. Lines 46-49:
let name = entry.name().to_string();
...
path: output_dir.join(name),
use the raw, unsanitized entry name. The path written to disk (sanitized) and the path recorded in the inventory (raw) are computed by two separate passes that can produce different results for the same entry.
Why this matters
A caller that consumes ExtractionResult.files to import, tag, or move the extracted files will silently skip every file whose recorded path does not match its real on-disk location. The extraction completes without error, yet the affected files are never processed — invisible data loss with no diagnostic. An archive can be crafted with such entry names to make specific files vanish from downstream handling while the operation still reports success, which under an adversarial threat model is an exploitable integrity gap.
Desired correction
Build the inventory by walking the actual filesystem under output_dir after extraction (the pattern already used in seven_zip.rs:collect_files) instead of re-reading archive entry names. Done when: a test with an entry named ../escape.flac shows the returned ExtractedFile.path still points inside output_dir and the file is accessible at that path.
Finding
ZipArchive::extract()sanitizes entry paths before writing (the zip crate strips leading slashes and resolves..). The inventory loop that runs afterward does not apply equivalent sanitization: it joins the rawentry.name()tooutput_dir. For any entry whose name contains path-unsafe components, the recordedExtractedFile.pathwill not match the actual filesystem location of the written file. The extraction pass and the inventory pass are independent and can disagree on the resulting path.Evidence
crates/ergasia/src/extract/zip_extract.rs:27:uses zip's sanitizing extractor. Lines 46-49:
use the raw, unsanitized entry name. The path written to disk (sanitized) and the path recorded in the inventory (raw) are computed by two separate passes that can produce different results for the same entry.
Why this matters
A caller that consumes
ExtractionResult.filesto import, tag, or move the extracted files will silently skip every file whose recorded path does not match its real on-disk location. The extraction completes without error, yet the affected files are never processed — invisible data loss with no diagnostic. An archive can be crafted with such entry names to make specific files vanish from downstream handling while the operation still reports success, which under an adversarial threat model is an exploitable integrity gap.Desired correction
Build the inventory by walking the actual filesystem under
output_dirafter extraction (the pattern already used inseven_zip.rs:collect_files) instead of re-reading archive entry names. Done when: a test with an entry named../escape.flacshows the returnedExtractedFile.pathstill points insideoutput_dirand the file is accessible at that path.