Fix: Calls to compression tools need to separate file names from options
Fixed BurntSushi/ripgrep#3222 — 2 line bug-fix.

The Bug
Repo: BurntSushi/ripgrep Issue: #3222 Status: submission-failed PR: https://github.com/BurntSushi/ripgrep/pull/3222
Description: Calls to compression tools need to separate file names from options
Fix scope: 2 lines changed in crates/cli/src/decompress.rs
When ripgrep encounters a compressed file, it does not perform decompression
internally. Instead, it delegates to external tools installed on the system —
gzip, bzip2, xz, lz4, brotli, zstd, and uncompress — by spawning
a child process with arguments like gzip -d -c <file>. This design keeps
ripgrep’s binary small and avoids linking against native compression libraries,
but it introduces a subtle class of bugs: file names that look like options.
A file named --help.gz or -v.gz would be passed directly as a positional
argument to the compression tool. Without a -- separator, the tool interprets
it as a flag, producing wrong output or failing entirely.
Code Analysis
The core of the decompression delegation lives in
crates/cli/src/decompress.rs. The DecompressionMatcher::command() method
(at line 179) looks up a pre-configured std::process::Command for a given
file path by matching its extension against a set of globs (e.g., *.gz maps
to gzip, *.bz2 maps to bzip2). The command already carries the
decompression flags (-d -c etc.) from
default_decompression_commands().
The actual invocation happens in
DecompressionReaderBuilder::build() (line 221). After obtaining the command
via self.matcher.command(path), the builder does:
cmd.arg(path);
This appends the file path as the final argument to the child process. The
problem is that cmd.arg() does not insert a -- terminator — the path is
placed verbatim into the argument vector. If the path starts with -, the
decompression tool will parse it as an option rather than a file name.
The default_decompression_commands() function (line 490) defines the full
set of supported tools: gzip, bzip2, xz (also used for LZMA via
--format=lzma), lz4, brotli, zstd, and BSD uncompress. Each entry
stores the binary name and its decompression flags. The glob-to-command
mapping covers .gz, .tgz, .bz2, .tbz2, .xz, .txz, .lz4,
.zst, .zstd, .br, and .Z files.
Root Cause
The root cause is a missing POSIX -- separator before the file path argument
at line 229 of crates/cli/src/decompress.rs. In POSIX convention, --
signals the end of option parsing — everything that follows is a positional
argument regardless of whether it starts with -. Many decompression tools
(for example gzip, xz, and bzip2) support this convention, but ripgrep
was not utilizing it.
This edge case is easy to miss because standard test suites rarely exercise file names that collide with option syntax. A user searching files in an untrusted or automatically generated directory could encounter files with leading dashes, causing the decompression subprocess to fail silently or produce corrupted output.
Impact: For ripgrep users who rely on the -z / --search-zip flag to
search compressed archives, encountering a file named with a leading dash
would cause the decompression tool to reject the argument, emit an error, and
produce no output for that file. Because ripgrep logs the error at debug level
by default, the failure could appear as a silent skip.
The Fix
The fix is a two-line addition in DecompressionReaderBuilder::build() that
inserts "--" before the file path argument:
cmd.arg("--");
cmd.arg(path);
This is a textbook application of the POSIX end-of-options idiom. The
"--" argument tells every POSIX-compliant tool that subsequent arguments
are file names, not options. The change is entirely additive — it does not
alter any existing behavior for normal file names (those without a leading
-) because -- is simply absorbed when there are no further options to
consume.
Pattern & Takeaways
Pattern: Unvalidated argument injection in subprocess argument vectors —
when user-controlled data (a file path) is passed to an external command
without a -- separator, the file name can be misinterpreted as an option.
This is a recurring class of bug in tools that delegate work to external
programs.
Key insight: Any time a program constructs a subprocess command with
user-influenced arguments, a -- terminator should be placed before the
positional arguments. This is well understood for shell commands but is just
as relevant when using std::process::Command in Rust, subprocess in
Python, or exec/spawn in any language.
Transfer Potential
High — the -- separator pattern is universal. Every tool that delegates
to external programs is susceptible to this bug. Examples include:
- Code formatters that invoke language-specific linters
- Build systems that call compilers with generated file names
- File managers that open documents with associated applications
- Archive tools that pass member names to decompressors
The same two-character fix — inserting "--" before the variable argument —
applies in every case.
Auto-generated from PR #3222. View all patches on GitHub.
References
[1] BurntSushi/ripgrep [2] #3222 [3] https://github.com/BurntSushi/ripgrep/pull/3222