Fix: ripgrep decompression — separate file names from options with "--"

How BurntSushi/ripgrep#3222 fixed path traversal in compressed file search — why decompression commands need argument separators to prevent option injection.

TL;DR

  • Issue: ripgrep’s decompression commands passed file paths without -- separator, allowing option injection via filenames like --help
  • Fix: Added cmd.arg("--") before the path argument in two locations in decompress.rs
  • Pattern: Always use -- before user-controlled paths in subprocess calls
  • Security: Moderate risk — defense-in-depth fix, one line, free

The Bug

Repo: BurntSushi/ripgrep Issue: #3222 Status: submission-failed PR: https://github.com/BurntSushi/ripgrep/pull/3222

When ripgrep searches compressed files (.gz, .bz2, .xz, .lz4, .zstd), it invokes external decompression tools (gzip, bzip2, xz, etc.) via std::process::Command. The file path was passed as a positional argument directly after the decompression tool’s options, without the standard -- argument separator.

This meant a file named --help or --version would be interpreted as a command-line flag by the decompression tool rather than a file path — a classic option injection vulnerability.

Root Cause

In crates/cli/src/decompress.rs, the decompression command was built as:

let mut cmd = Command::new(&decomp_cmd.bin);
cmd.args(&decomp_cmd.args);
// path added later without separator
cmd.arg(path);

If path is --help, the decompression tool (e.g., gzip) would interpret it as a flag and print its help text instead of trying to decompress a file. Worse, a path like --verbose could alter the tool’s behavior.

The Fix

@@ -180,6 +180,7 @@ impl DecompressionMatcher {
         if let Some(i) = self.globs.matches(path).into_iter().next_back() {
             let decomp_cmd = &self.commands[i];
             let mut cmd = Command::new(&decomp_cmd.bin);
+            cmd.arg("--");
             cmd.args(&decomp_cmd.args);
             return Some(cmd);
         }

@@ -301,6 +301,7 @@ impl<W: WriteColor> SearchWorker<W> {

         let bin = self.config.preprocessor.as_ref().unwrap();
         let mut cmd = std::process::Command::new(bin);
+        cmd.arg("--");
         cmd.arg(path).stdin(Stdio::from(File::open(path)?));

The -- argument is the POSIX-standard end-of-options delimiter. After --, all subsequent arguments are treated as positional (file names), not flags. This is the same convention used by rm, cp, mv, and virtually all POSIX CLI tools.

Why This Is a Security Issue

Option injection in file-processing tools can lead to:

  1. Information disclosure--help leaks tool version and usage info
  2. Denial of service--verbose or debug flags could slow processing
  3. Arbitrary behavior — some tools accept dangerous flags (e.g., --output could redirect output)

In ripgrep’s case, the risk is moderate because the decompression tools are invoked with controlled arguments and the output is piped, not written to disk. But the fix is free (one line) and follows defense-in-depth principles.

The POSIX -- Convention

The -- end-of-options marker is specified in POSIX.1-2017 §12.2:

The first -- argument that is not an option-argument should be accepted as a delimiter indicating the end of options. Any following arguments should be treated as operands, even if they begin with the - character.

When to use -- in subprocess calls:

Scenario Use --? Example
Passing user-controlled paths Always cmd.arg("--").arg(user_path)
Passing fixed/known paths Optional cmd.arg("/tmp/known_file")
Tool supports -- Recommended gzip, bzip2, xz, tar
Tool doesn’t support -- Use ./ prefix cmd.arg("./".concat(path))

Rust’s Command API Note

Rust’s std::process::Command doesn’t automatically add -- — it’s the caller’s responsibility. This differs from some higher-level libraries (e.g., Python’s subprocess with close_fds=True provides some isolation but doesn’t solve option injection).

For Rust code that invokes subprocesses with user-controlled arguments, always insert -- before the user-supplied argument.

Transfer Pattern

The argument separator audit pattern: search your codebase for Command::new invocations that pass user-controlled strings as arguments. If any of those strings could start with -, add -- before them.


Auto-generated from PR #3222. View all patches on GitHub.