CVE-2021-36376
Description
dandavison delta before 0.8.3 on Windows resolves an executable's pathname as a relative path from the current directory.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
Delta before 0.8.3 on Windows resolves pager executable paths relative to current directory, enabling local arbitrary code execution.
Vulnerability
CVE-2021-36376 affects dandavison delta versions prior to 0.8.3 on Windows. The vulnerability resides in the way delta resolves the path to the pager executable (e.g., less). When the pager command is specified as a relative path (e.g., less), delta resolves it from the current working directory rather than searching the system PATH [1]. This allows an attacker to place a malicious executable with the same name in the current directory, which delta will execute instead of the legitimate system tool.
Exploitation
An attacker needs to place a malicious executable (e.g., less.exe) in a directory where a target user will run delta. When the user executes delta in that directory, delta resolves the pager path relative to the current directory and launches the attacker's executable. No authentication is required; the attacker only needs write access to a directory the user will visit. The user must be running delta with a pager configured (default less).
Impact
Successful exploitation results in arbitrary code execution in the context of the user running delta. The attacker gains the same privileges as the user, which could lead to data theft, installation of malware, or full system compromise.
Mitigation
The vulnerability is fixed in version 0.8.3 [2]. Users should update to delta 0.8.3 or later. No workarounds are documented; updating is the recommended action.
AI Insight generated on May 21, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
git-deltacrates.io | < 0.8.3 | 0.8.3 |
Affected products
3- dandavison/deltadescription
- ghsa-coords2 versions
< 0.8.3+ 1 more
- (no CPE)range: < 0.8.3
- (no CPE)range: < 0.8.3-1.2
Patches
1f01846bd443aDo not resolve executables as relative path from current directory (#658)
5 files changed · +161 −66
Cargo.lock+57 −0 modified@@ -95,6 +95,17 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ea27d8d5fd867b17523bf6788b1175fa9867f34669d057e9adaf76e27bcea44b" +[[package]] +name = "bstr" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a40b47ad93e1a5404e6c18dec46b628214fee441c70f4ab5d6942142cc268a3d" +dependencies = [ + "lazy_static", + "memchr", + "regex-automata", +] + [[package]] name = "bytelines" version = "2.2.2" @@ -272,6 +283,7 @@ dependencies = [ "dirs-next", "error-chain", "git2", + "grep-cli", "itertools", "lazy_static", "pathdiff", @@ -298,6 +310,36 @@ dependencies = [ "url", ] +[[package]] +name = "globset" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10463d9ff00a2a068db14231982f5132edebad0d7660cd956a1c30292dbcbfbd" +dependencies = [ + "aho-corasick", + "bstr", + "fnv", + "log", + "regex", +] + +[[package]] +name = "grep-cli" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dd110c34bb4460d0de5062413b773e385cbf8a85a63fc535590110a09e79e8a" +dependencies = [ + "atty", + "bstr", + "globset", + "lazy_static", + "log", + "regex", + "same-file", + "termcolor", + "winapi-util", +] + [[package]] name = "hashbrown" version = "0.8.2" @@ -600,6 +642,12 @@ dependencies = [ "regex-syntax", ] +[[package]] +name = "regex-automata" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" + [[package]] name = "regex-syntax" version = "0.6.22" @@ -724,6 +772,15 @@ dependencies = [ "yaml-rust", ] +[[package]] +name = "termcolor" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4" +dependencies = [ + "winapi-util", +] + [[package]] name = "terminal_size" version = "0.1.15"
Cargo.toml+1 −0 modified@@ -23,6 +23,7 @@ box_drawing = "0.1.2" bytelines = "2.2.2" console = "0.14.1" dirs-next = "2.0.0" +grep-cli = "0.1.6" itertools = "0.10.1" lazy_static = "1.4" pathdiff = "0.2.0"
src/bat_utils/less.rs+6 −2 modified@@ -1,8 +1,12 @@ use std::process::Command; pub fn retrieve_less_version() -> Option<usize> { - let cmd = Command::new("less").arg("--version").output().ok()?; - parse_less_version(&cmd.stdout) + if let Ok(less_path) = grep_cli::resolve_binary("less") { + let cmd = Command::new(less_path).arg("--version").output().ok()?; + parse_less_version(&cmd.stdout) + } else { + None + } } fn parse_less_version(output: &[u8]) -> Option<usize> {
src/bat_utils/output.rs+92 −63 modified@@ -77,78 +77,35 @@ impl OutputType { let pagerflags = shell_words::split(&pager).chain_err(|| "Could not parse pager command.")?; - match pagerflags.split_first() { + Ok(match pagerflags.split_first() { Some((pager_name, args)) => { let pager_path = PathBuf::from(pager_name); let is_less = pager_path.file_stem() == Some(&OsString::from("less")); - let mut process = if is_less { - let mut p = Command::new(&pager_path); - if args.is_empty() || replace_arguments_to_less { - p.args(vec!["--RAW-CONTROL-CHARS"]); - - // Passing '--no-init' fixes a bug with '--quit-if-one-screen' in older - // versions of 'less'. Unfortunately, it also breaks mouse-wheel support. - // - // See: http://www.greenwoodsoftware.com/less/news.530.html - // - // For newer versions (530 or 558 on Windows), we omit '--no-init' as it - // is not needed anymore. - match retrieve_less_version() { - None => { - p.arg("--no-init"); - } - Some(version) - if (version < 530 || (cfg!(windows) && version < 558)) => - { - p.arg("--no-init"); - } - _ => {} - } - - if quit_if_one_screen { - p.arg("--quit-if-one-screen"); - } - } else { - p.args(args); - } - p.env("LESSCHARSET", "UTF-8"); - p + let process = if is_less { + _make_process_from_less_path( + pager_path, + args, + replace_arguments_to_less, + quit_if_one_screen, + config, + ) } else { - if pager_path.file_stem() == Some(&OsString::from("delta")) { - eprintln!( - "\ -It looks like you have set delta as the value of $PAGER. \ -This would result in a non-terminating recursion. \ -delta is not an appropriate value for $PAGER \ -(but it is an appropriate value for $GIT_PAGER)." - ); - std::process::exit(1); - } - let mut p = Command::new(&pager_path); - p.args(args); - p + _make_process_from_pager_path(pager_path, args) }; - if is_less && config.navigate { - if let Ok(hist_file) = - navigate::copy_less_hist_file_and_append_navigate_regexp(config) - { - process.env("LESSHISTFILE", hist_file); - if config.show_themes { - process.arg("+n"); - } - } + if let Some(mut process) = process { + process + .stdin(Stdio::piped()) + .spawn() + .map(OutputType::Pager) + .unwrap_or_else(|_| OutputType::stdout()) + } else { + OutputType::stdout() } - Ok(process - .env("LESSANSIENDCHARS", "mK") - .stdin(Stdio::piped()) - .spawn() - .map(OutputType::Pager) - .unwrap_or_else(|_| OutputType::stdout())) } - None => Ok(OutputType::stdout()), - } + None => OutputType::stdout(), + }) } fn stdout() -> Self { @@ -166,6 +123,78 @@ delta is not an appropriate value for $PAGER \ } } +fn _make_process_from_less_path( + less_path: PathBuf, + args: &[String], + replace_arguments_to_less: bool, + quit_if_one_screen: bool, + config: &config::Config, +) -> Option<Command> { + if let Ok(less_path) = grep_cli::resolve_binary(less_path) { + let mut p = Command::new(&less_path); + if args.is_empty() || replace_arguments_to_less { + p.args(vec!["--RAW-CONTROL-CHARS"]); + + // Passing '--no-init' fixes a bug with '--quit-if-one-screen' in older + // versions of 'less'. Unfortunately, it also breaks mouse-wheel support. + // + // See: http://www.greenwoodsoftware.com/less/news.530.html + // + // For newer versions (530 or 558 on Windows), we omit '--no-init' as it + // is not needed anymore. + match retrieve_less_version() { + None => { + p.arg("--no-init"); + } + Some(version) if (version < 530 || (cfg!(windows) && version < 558)) => { + p.arg("--no-init"); + } + _ => {} + } + + if quit_if_one_screen { + p.arg("--quit-if-one-screen"); + } + } else { + p.args(args); + } + p.env("LESSCHARSET", "UTF-8"); + p.env("LESSANSIENDCHARS", "mK"); + if config.navigate { + if let Ok(hist_file) = navigate::copy_less_hist_file_and_append_navigate_regexp(config) + { + p.env("LESSHISTFILE", hist_file); + if config.show_themes { + p.arg("+n"); + } + } + } + Some(p) + } else { + None + } +} + +fn _make_process_from_pager_path(pager_path: PathBuf, args: &[String]) -> Option<Command> { + if pager_path.file_stem() == Some(&OsString::from("delta")) { + eprintln!( + "\ +It looks like you have set delta as the value of $PAGER. \ +This would result in a non-terminating recursion. \ +delta is not an appropriate value for $PAGER \ +(but it is an appropriate value for $GIT_PAGER)." + ); + std::process::exit(1); + } + if let Ok(pager_path) = grep_cli::resolve_binary(pager_path) { + let mut p = Command::new(&pager_path); + p.args(args); + Some(p) + } else { + None + } +} + impl Drop for OutputType { fn drop(&mut self) { if let OutputType::Pager(ref mut command) = *self {
src/main.rs+5 −1 modified@@ -132,7 +132,11 @@ You can also use delta to diff two files: `delta file_A file_B`." let diff_command = "git"; let minus_file = minus_file.unwrap_or_else(die); let plus_file = plus_file.unwrap_or_else(die); - let mut diff_process = process::Command::new(PathBuf::from(diff_command)) + let diff_command_path = match grep_cli::resolve_binary(PathBuf::from(diff_command)) { + Ok(path) => path, + Err(_) => return config.error_exit_code, + }; + let mut diff_process = process::Command::new(diff_command_path) .args(&["diff", "--no-index"]) .args(&[minus_file, plus_file]) .stdout(process::Stdio::piped())
Vulnerability mechanics
Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
6- github.com/advisories/GHSA-5xg3-j2j6-rcx4ghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2021-36376ghsaADVISORY
- github.com/dandavison/delta/commit/f01846bd443aaf92fdd5ac20f461beac3f6ee3fdghsax_refsource_MISCWEB
- github.com/dandavison/delta/releases/tag/0.8.3ghsax_refsource_CONFIRMWEB
- rustsec.org/advisories/RUSTSEC-2021-0105.htmlghsaWEB
- vuln.ryotak.me/advisories/54ghsax_refsource_MISCWEB
News mentions
0No linked articles in our index yet.