Skip to main content

exarch-cli

Extract, create, list, and verify TAR (plus gz/bz2/xz/zst), ZIP, and 7z archives securely using the `exarch` CLI. Use whenever a task involves unpacking or building archives, especially from untrusted sources — exarch blocks path traversal, symlink/hardlink escapes, zip bombs, and oversized archives by default, and reports rejections as structured errors instead of silently succeeding.

Source facts

Repository
bug-ops/exarch
Last source activity
September 1, 2026 at 22:23
Detected SKILL.md language
English
Stars
5
Forks
3

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

Showing SKILL.md

SKILL.md
Source instructions · Read-only preview
name
exarch-cli
description
Extract, create, list, and verify TAR (plus gz/bz2/xz/zst), ZIP, and 7z archives securely using the `exarch` CLI. Use whenever a task involves unpacking or building archives, especially from untrusted sources — exarch blocks path traversal, symlink/hardlink escapes, zip bombs, and oversized archives by default, and reports rejections as structured errors instead of silently succeeding.
license
MIT OR Apache-2.0
compatibility
Requires the `exarch` binary (cargo install exarch-cli, or build from this workspace with cargo build -p exarch-cli). MSRV 1.98.0 if building from source.
metadata
{"author":"bug-ops","version":"0.6.1"}
# exarch CLI `exarch` is a security-first archive tool. It extracts, creates, lists, and verifies TAR (plain, `.gz`, `.bz2`, `.xz`, `.zst`), ZIP, and 7z archives. Every extraction runs through a validation pipeline that is deny-by-default: path traversal, symlink/hardlink escapes outside the destination directory, zip bombs (excessive compression ratio), oversized files/archives, and unsafe permission bits (setuid/setgid, world-writable) are all rejected unless explicitly allowed. If an extraction fails with a security-related error, treat that as the tool doing its job, not a bug to route around (see "Do not reach for `--allow-*` reflexively" below). ## Install First check whether a Rust toolchain is present: `command -v cargo`. **`cargo` available (recommended):** ```bash cargo install exarch-cli ``` This installs a binary named `exarch` (not `exarch-cli`) and is the fastest path to a working binary since it doesn't require identifying the target triple. Inside this workspace, use the debug build instead: ```bash cargo build -p exarch-cli ./target/debug/exarch --help ``` **`cargo` NOT available:** *Linux / macOS* — run the install script, no toolchain required, checksum-verified: ```bash curl -fsSL https://raw.githubusercontent.com/bug-ops/exarch/main/scripts/install.sh | sh ``` Installs to `~/.local/bin` (override with `EXARCH_INSTALL_DIR`). If the script itself can't run (e.g. a restricted shell), fall back to a manual download: get `exarch-<version>-<target>.tar.gz` plus its `.sha256` from [GitHub Releases](https://github.com/bug-ops/exarch/releases) — `<target>` is `x86_64-unknown-linux-gnu`, `aarch64-unknown-linux-gnu`, `x86_64-apple-darwin`, or `aarch64-apple-darwin` — then verify and extract: ```bash sha256sum -c exarch-<version>-<target>.tar.gz.sha256 # macOS: shasum -a 256 -c tar -xzf exarch-<version>-<target>.tar.gz ``` *Windows* — no install script; download `exarch-<version>-x86_64-pc-windows-msvc.zip` plus its `.sha256` from [GitHub Releases](https://github.com/bug-ops/exarch/releases), then verify and extract in PowerShell: ```powershell (Get-FileHash exarch-<version>-x86_64-pc-windows-msvc.zip -Algorithm SHA256).Hash # compare (case-insensitively) against the hash in the .sha256 file, then: Expand-Archive exarch-<version>-x86_64-pc-windows-msvc.zip -DestinationPath . ``` `CertUtil -hashfile exarch-<version>-x86_64-pc-windows-msvc.zip SHA256` is an equivalent, cmd-only alternative to `Get-FileHash`. ## Global flags Available on every subcommand: | Flag | Effect | |---|---| | `-v, --verbose` | Verbose output. Conflicts with `--quiet`. | | `-q, --quiet` | Suppress non-error output. Conflicts with `--verbose`. | | `-j, --json` | Emit a machine-readable JSON envelope instead of human-readable text (see below). Always prefer this when parsing output programmatically. | ## Subcommands ### `extract <ARCHIVE> [OUTPUT_DIR]` ```bash exarch extract archive.tar.gz ./out --json ``` `OUTPUT_DIR` defaults to the **current directory** if omitted — always pass an explicit output directory rather than relying on the default, so extraction is predictable and easy to clean up. Key flags (all optional): | Flag | Default | Purpose | |---|---|---| | `--force` | off | Overwrite existing files at the destination. | | `--atomic` | off | Extract to a temp dir, rename into place on success, clean up on failure. With `--force` and a pre-existing destination, the swap is a backup-then-replace: the existing destination is renamed aside first, the new content is renamed into its place second, and only once that swap succeeds is the old destination removed — never before, so a failure at any point still leaves either the old or the new content in place under the destination name. On Unix, `--force` also requires read permission on the destination's parent directory (used to pin it by file descriptor for the swap); the destination itself does not need to be readable. With `--force`, a destination that is itself a symlink is now rejected — pass the resolved target path instead. Without `--force`, a symlinked destination isn't rejected as such, but the final rename onto it fails (`ENOTDIR` on Unix, `OutputExists` on Windows), since `rename` doesn't follow a symlink at the target — pass the resolved target path here too. Plain (non-atomic) `extract` is unaffected: it resolves a symlinked destination via `canonicalize()` and writes through it, matching `tar -C`/`unzip -d` (see #533). If a failed `--atomic --force` run leaves temp/backup content behind (e.g. an intermediate parent-path component was redirected mid-extraction), the error message discloses that content's actual location — see #530. | | `--preserve-permissions` | off | Preserve file permissions from the archive (setuid/setgid are still stripped regardless). | | `--max-files` | 10000 | Max number of entries to extract. | | `--max-total-size` | 500 MB | Max cumulative extracted size. Accepts `K`/`M`/`G`/`T` suffixes, e.g. `2G`. | | `--max-file-size` | 50 MB | Max size of any single extracted file. Same suffix syntax. | | `--max-compression-ratio` | 100 | Max uncompressed/compressed ratio before an entry is treated as a zip bomb. | | `--max-path-depth` | 32 | Max path component depth allowed per entry. | | `--allowed-extensions <EXT>` | all allowed | Restrict extraction to matching extensions. Repeatable or comma-separated (`txt,pdf`); leading dot optional, case-insensitive. | | `--allow-symlinks` | off | Allow symlinks that resolve within the extraction directory. | | `--allow-hardlinks` | off | Allow hardlinks that resolve within the extraction directory. | | `--allow-absolute-paths` | off | Allow archive entries with absolute paths. | | `--allow-world-writable` | off | Allow files with world-writable (mode `0o002`) permission bits. | | `--allow-solid-archives` | off | Allow solid 7z archives (see Security model below). | | `--banned-component <COMPONENT>` | see below | Block entries whose path contains this component. **Replaces** the default list — see warning below. Repeatable. | > [!WARNING] > **`--banned-component` REPLACES the default ban list, it does not append to it.** The default > blocks path components `.git`, `.ssh`, `.gnupg`, `.aws`, `.kube`, `.docker`, `.env`. Passing > `--banned-component foo` drops ALL of those defaults and blocks only `foo` — silently letting > `.ssh`, `.aws`, etc. through. If you need one extra banned component, repeat the flag with the > full desired set, not just the addition: `--banned-component .git --banned-component .ssh > --banned-component .gnupg --banned-component .aws --banned-component .kube --banned-component > .docker --banned-component .env --banned-component foo`. Passing an empty value disables all > banning. ### `create <OUTPUT> <SOURCE>...` ```bash exarch create out.tar.gz src/ --json ``` | Flag | Default | Purpose | |---|---|---| | `-f, --force` | off | Overwrite `OUTPUT` if it already exists. | | `-l, --compression-level <1-9>` | format default | Compression level. | | `-x, --exclude <PATTERN>` | none | Glob exclude pattern; repeatable. | | `--strip-prefix <PREFIX>` | none | Strip a path prefix from entry names. | | `--include-hidden` | off | Include dotfiles/hidden entries. | | `--follow-symlinks` | off | Follow symlinks when walking source trees (stores target contents, not the link). | | `--max-file-size <BYTES>` | none | Skip source files larger than this (`K`/`M`/`G`/`T` suffixes). | | `--preserve-permissions[=true\|false]` | `true` | Preserve platform permission bits. Pass `--preserve-permissions=false` for a portable, permission-agnostic archive. | > [!NOTE] > With `--follow-symlinks` off (default), a symlink passed directly as a source (file or > directory) is archived as a link in TAR; ZIP has no symlink entry type, so it is skipped > instead (warning + `files_skipped`). With `--follow-symlinks` on, both TAR and ZIP dereference > the symlink and store the target's content — for a directory symlink, this means walking and > archiving the entire target tree. `exarch` refuses to *create* 7z archives (7z is extract-only) and refuses to create archives under ZIP-family extensions that carry extra format semantics: `.jar`, `.war`, `.ear`, `.nar`, `.nbm`, `.apk`, `.aab`, `.ipa`, `.appx`, `.msix`, `.whl`, `.vsix`, `.xpi`, `.epub` — use plain `.zip` for those cases. ### `list <ARCHIVE>` ```bash exarch list archive.zip -l -H --json ``` | Flag | Default | Purpose | |---|---|---| | `-l, --long` | off | Detailed per-entry info (type, size, mode, timestamps, link targets). | | `-H, --human-readable` | off | Human-readable sizes in text mode (ignored in `--json` mode, which always emits raw byte counts). | | `--max-files` | 10000 | Hard limit on entries scanned; listing fails with `QuotaExceeded` past this count, it does not truncate. | | `--max-total-size` | 500 MB | Hard limit on cumulative listed size; listing fails with `QuotaExceeded` past this size. Raise it explicitly for larger archives. | | `--max-file-size` | 50 MB | Hard limit on any single entry's size; listing fails with `QuotaExceeded` (`FileSize`) past this size. Raise it explicitly for archives with large individual files. | | `--allow-solid-archives` | off | Allow listing solid 7z archives (higher memory use). | Verified live: a 51 MB tar entry fails `exarch list` with `"quota exceeded: single file size (53477376 > 52428800)"` at the 50 MB default, and succeeds once `--max-file-size 100M` is passed. ### `verify <ARCHIVE>` ```bash exarch verify archive.tar.gz --check-integrity --check-security --json ``` | Flag | Default | Purpose | |---|---|---| | `--check-integrity` | off | Validate checksums/structure. | | `--check-security` | off | Run the security validation pipeline (path traversal, symlink/hardlink, permissions) without extracting. | | `--strict` | off | Treat any warning-level finding as an error and exit with status `2` instead of `0`. | | `--max-files` / `--max-total-size` / `--max-file-size` / `--allow-solid-archives` | same as `list` (10000 / 500 MB / 50 MB / off) | Same hard-limit semantics as `list` for file count and total size — verification fails with `QuotaExceeded` past those two caps, it does not skip entries. | Run `verify` before `extract` on any archive from an untrusted source to inspect issues without writing anything to disk. Archives over 500 MB need `--max-total-size` raised explicitly or `verify` will fail with `QuotaExceeded` before it can report anything else. `--max-file-size` is the one exception to "fails with `QuotaExceeded`": an entry over this cap does **not** abort `verify` early. `verify`'s internal pre-flight listing pass always allows entries of any size through, then re-checks the real `--max-file-size` per entry and records a violation as a `HIGH`-severity `QuotaExceeded` issue, same as any other security finding — `data.status` becomes `"FAIL"` with the issue itemized, `status` (the envelope) stays `"success"`, same as the `verify` "one exception" rule described under `--json` output envelope below. Verified live: a 51 MB tar entry against the 50 MB default produces `data.status: "FAIL"` with one `HIGH` `"Quota Exceeded"` issue and exit code `1`; the same command with `--max-file-size 100M` produces `data.status: "PASS"` and exit code `0`. When the archive has `status: "FAIL"`, `verify --json` prints exactly one top-level JSON document to stdout — a `status: "success"` envelope carrying the full report (`data.status: "FAIL"`, findings in `data.issues`). Verified live on an archive containing a bare symlink entry: a single valid JSON document, parseable with a plain `json.loads(stdout)`. The process still exits `1` for a FAIL status. ### `completion <SHELL>` ```bash exarch completion zsh > ~/.zsh/completions/_exarch ``` Valid `<SHELL>` values (case-sensitive, exact set): `bash`, `elvish`, `fish`, `powershell`, `zsh`. ## Do not reach for `--allow-*` reflexively `--allow-symlinks`, `--allow-hardlinks`, `--allow-absolute-paths`, `--allow-world-writable`, `--allow-solid-archives`, and `--force` all weaken a default-deny security check. When an extraction fails with `SymlinkEscape`, `HardlinkEscape`, `PathTraversal`, `ZipBomb`, `InvalidPermissions`, or `SecurityViolation`, that is the validator correctly rejecting something dangerous — **do not add the matching `--allow-*` flag just to make the command succeed.** Only add it when the archive's origin and contents are already known to be trusted, and prefer re-running just `verify --check-security` first to see the full list of findings before deciding. `--force` (extract/create) and `--atomic` (extract) are safe to use more liberally since they affect overwrite behavior, not path/link/permission validation. ## `--json` output envelope Every subcommand's `--json` output uses the same envelope: ```jsonc // success { "operation": "extract" | "create" | "list" | "verify", "status": "success", "data": { /* per-command payload, see below */ } } // error { "operation": "extract" | "create" | "list" | "verify", "status": "error",
View on GitHub
This SKILL.md is very large, so SkillsMP previews the first section here. View on GitHub