unpacksort
unpacksort safely recovers files from an mbox or a directory, recursively opens
supported archives and attached messages, removes byte-identical duplicates, and
publishes a deterministic type-grouped result with complete provenance.
It supports Python 3.12+, ZIP/ZIP64, TAR (plain, gzip, bzip2, xz, and zstandard), 7z, parser-validated PDFs, and common ZIP application packages. RAR is detected and retained as unprocessed; source links and archive links are never followed.
Status
Not yet published. semantic-release has staged the first release and the protected release is waiting on PyPI trusted-publisher setup. No PyPI, Homebrew, or WinGet release is claimed until those channels exist — the install commands below describe the intended routes, not ones you can run today.
Overview
unpacksort recovers files from a mailbox or a directory tree: it opens nested
archives and attached messages, removes byte-identical duplicates, and publishes
a deterministic, type-grouped result with a manifest recording where every file
came from.
Deterministic means the same input produces the same output, every time — which is what makes a recovery run something you can check rather than something you have to trust.
Install
pipx install unpacksort
Future releases also provide brew install fileworks/tap/unpacksort, a Windows
x64 portable ZIP, and winget install fileworks.unpacksort. Initial executable
artifacts are unsigned and can trigger an operating-system trust prompt. Verify
the published SHA-256 checksum, while remembering that a checksum detects damage
but does not independently prove who published a file.
Quick start
unpacksort ~/Mail/archive.mbox ~/Recovered
unpacksort ~/Downloads ~/Recovered --flatten
unpacksort ~/Mail/archive.mbox ~/Recovered-PDFs --pdf-only
Hierarchy mode is the default. It preserves source, message, and archive ancestry
beneath fixed type groups. Flatten mode publishes directly beneath each group;
distinct collisions are named name.ext, name_1.ext, and so on. Byte-identical
occurrences reference one canonical file and do not consume suffixes.
A successful run writes manifest.jsonl and report.txt. Exit 1 means the
result is trustworthy but partial—for example because an encrypted, corrupt,
unsafe, limit-blocked, or unsupported item was retained or reported. Re-run the
same command to resume compatible committed work.
See the operating manual, release and channel setup, and security policy.
Safety model
Inputs are treated as personal but potentially malformed. Extraction is bounded, staged privately, content-addressed, and published atomically. Archive paths never directly control public paths. The initial release does not isolate parsers in a process or VM and is not a malware scanner.
Development
uv sync --locked --all-groups
uv run ruff format --check .
uv run ruff check .
uv run mypy
uv run pytest
Use an ignored CLAUDE.local.md at the repository root for per-clone paths,
commands, or private preferences. Never store credentials or other secrets
there.
Licensed under the MIT License.
Usage
unpacksort SOURCE DESTINATION [options]
| Option | Effect |
|---|---|
--flatten |
One directory per type instead of mirroring the source layout |
--pdf-only |
Extract and validate PDFs, ignore everything else |
--log-file PATH |
Write bounded rotating progress and diagnostics (default: beside destination) |
--verbose |
Include debug diagnostics in the logfile |
unpacksort --help is authoritative.
Configuration
There is no configuration file. Behaviour and all seven safety limits are set by
explicit command-line flags; run unpacksort --help for their names, defaults,
and minimum values. Raising a limit is an operator decision and expands the
resource budget for untrusted input, so scheduled jobs should pin reviewed
values rather than accept input-controlled arguments.
Troubleshooting
A RAR archive was not extracted. RAR is detected and retained unprocessed;
unpacksort does not bundle a RAR implementation.
The run stopped at a safety limit. The manifest names which limit and which container tripped it. That is the intended behaviour for a suspicious archive.
A PDF was rejected. PDFs are parser-validated; a file that cannot be parsed is retained as-is rather than published as a valid document.
The output differs between two runs of the same input. It should not. That is a bug worth reporting, with the two manifests.
Security
Report vulnerabilities privately as described in SECURITY.md.
Parsers run in-process: unpacksort bounds extraction and never follows links,
but it is not a malware scanner or a sandbox. Process hostile data inside an
additional operating-system sandbox.
License
MIT — see LICENSE.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file unpacksort-1.1.0.tar.gz.
File metadata
- Download URL: unpacksort-1.1.0.tar.gz
- Upload date:
- Size: 179.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d9f721056fb853d5dd351a8cf7ffdb837368a87159ee490fc2cee5b9dcf1e6e5
|
|
| MD5 |
c0f7ef47b6595b4ced898c217b96e026
|
|
| BLAKE2b-256 |
4c929783021eb514461d542cca6a11a3877be470cc3d284a074abe1c58f91102
|
Provenance
The following attestation bundles were made for unpacksort-1.1.0.tar.gz:
Publisher:
release.yml on fileworks/unpacksort
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
unpacksort-1.1.0.tar.gz -
Subject digest:
d9f721056fb853d5dd351a8cf7ffdb837368a87159ee490fc2cee5b9dcf1e6e5 - Sigstore transparency entry: 2311564201
- Sigstore integration time:
-
Permalink:
fileworks/unpacksort@27aaf0d3cd49b7aeac3ba5ff5722fba0e8605ebb -
Branch / Tag:
refs/heads/main - Owner: https://github.com/fileworks
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@27aaf0d3cd49b7aeac3ba5ff5722fba0e8605ebb -
Trigger Event:
push
-
Statement type:
File details
Details for the file unpacksort-1.1.0-py3-none-any.whl.
File metadata
- Download URL: unpacksort-1.1.0-py3-none-any.whl
- Upload date:
- Size: 42.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
54c831b303a01488d4d996d75a52d5f61d3104ff5714620134e5ea827b6b6fdd
|
|
| MD5 |
35d654837f916c0660b54672b71a6efd
|
|
| BLAKE2b-256 |
3bcc515c52e160ccd4860bee40eb8ee3ae98c51e43bc90c42bccc90ccb525390
|
Provenance
The following attestation bundles were made for unpacksort-1.1.0-py3-none-any.whl:
Publisher:
release.yml on fileworks/unpacksort
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
unpacksort-1.1.0-py3-none-any.whl -
Subject digest:
54c831b303a01488d4d996d75a52d5f61d3104ff5714620134e5ea827b6b6fdd - Sigstore transparency entry: 2311564215
- Sigstore integration time:
-
Permalink:
fileworks/unpacksort@27aaf0d3cd49b7aeac3ba5ff5722fba0e8605ebb -
Branch / Tag:
refs/heads/main - Owner: https://github.com/fileworks
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@27aaf0d3cd49b7aeac3ba5ff5722fba0e8605ebb -
Trigger Event:
push
-
Statement type: